1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

od: Changes command line parser to clap (#1849)

This commit is contained in:
pedrohjordao 2021-03-21 15:19:30 +00:00 committed by GitHub
parent f60790dd41
commit ca8fbc37bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 284 additions and 123 deletions

4
Cargo.lock generated
View file

@ -1,5 +1,3 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "advapi32-sys"
version = "0.2.0"
@ -1868,7 +1866,7 @@ name = "uu_od"
version = "0.0.4"
dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"half 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7",

View file

@ -16,7 +16,7 @@ path = "src/od.rs"
[dependencies]
byteorder = "1.3.2"
getopts = "0.2.18"
clap = "2.33"
half = "1.6"
libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }

View file

@ -41,15 +41,18 @@ use crate::parse_nrofbytes::parse_number_of_bytes;
use crate::partialreader::*;
use crate::peekreader::*;
use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION");
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
static ABOUT: &str = "dump files in octal and other formats";
static USAGE: &str = r#"Usage:
static USAGE: &str = r#"
od [OPTION]... [--] [FILENAME]...
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#;
static LONG_HELP: &str = r#"
Displays data in various human-readable formats. If multiple formats are
specified, the output will contain all formats in the order they appear on the
command line. Each format will be printed on a new line. Only the line
@ -88,85 +91,18 @@ Any type specification can have a "z" suffix, which will add a ASCII dump at
If an error occurred, a diagnostic message will be printed to stderr, and the
exitcode will be non-zero."#;
fn create_getopts_options() -> getopts::Options {
let mut opts = getopts::Options::new();
opts.optopt(
"A",
"address-radix",
"Select the base in which file offsets are printed.",
"RADIX",
);
opts.optopt(
"j",
"skip-bytes",
"Skip bytes input bytes before formatting and writing.",
"BYTES",
);
opts.optopt(
"N",
"read-bytes",
"limit dump to BYTES input bytes",
"BYTES",
);
opts.optopt(
"",
"endian",
"byte order to use for multi-byte formats",
"big|little",
);
opts.optopt(
"S",
"strings",
"output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.",
"BYTES",
);
opts.optflagmulti("a", "", "named characters, ignoring high-order bit");
opts.optflagmulti("b", "", "octal bytes");
opts.optflagmulti("c", "", "ASCII characters or backslash escapes");
opts.optflagmulti("d", "", "unsigned decimal 2-byte units");
opts.optflagmulti("D", "", "unsigned decimal 4-byte units");
opts.optflagmulti("o", "", "octal 2-byte units");
opts.optflagmulti("I", "", "decimal 8-byte units");
opts.optflagmulti("L", "", "decimal 8-byte units");
opts.optflagmulti("i", "", "decimal 4-byte units");
opts.optflagmulti("l", "", "decimal 8-byte units");
opts.optflagmulti("x", "", "hexadecimal 2-byte units");
opts.optflagmulti("h", "", "hexadecimal 2-byte units");
opts.optflagmulti("O", "", "octal 4-byte units");
opts.optflagmulti("s", "", "decimal 2-byte units");
opts.optflagmulti("X", "", "hexadecimal 4-byte units");
opts.optflagmulti("H", "", "hexadecimal 4-byte units");
opts.optflagmulti("e", "", "floating point double precision (64-bit) units");
opts.optflagmulti("f", "", "floating point single precision (32-bit) units");
opts.optflagmulti("F", "", "floating point double precision (64-bit) units");
opts.optmulti("t", "format", "select output format or formats", "TYPE");
opts.optflag(
"v",
"output-duplicates",
"do not use * to mark line suppression",
);
opts.optflagopt(
"w",
"width",
"output BYTES bytes per output line. 32 is implied when BYTES is not \
specified.",
"BYTES",
);
opts.optflag("", "help", "display this help and exit.");
opts.optflag("", "version", "output version information and exit.");
opts.optflag(
"",
"traditional",
"compatibility mode with one input, offset and label.",
);
opts
pub(crate) mod options {
pub const ADDRESS_RADIX: &str = "address-radix";
pub const SKIP_BYTES: &str = "skip-bytes";
pub const READ_BYTES: &str = "read-bytes";
pub const ENDIAN: &str = "endian";
pub const STRINGS: &str = "strings";
pub const FORMAT: &str = "format";
pub const OUTPUT_DUPLICATES: &str = "output-duplicates";
pub const TRADITIONAL: &str = "traditional";
pub const WIDTH: &str = "width";
pub const VERSION: &str = "version";
pub const FILENAME: &str = "FILENAME";
}
struct OdOptions {
@ -182,8 +118,8 @@ struct OdOptions {
}
impl OdOptions {
fn new(matches: getopts::Matches, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) {
fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native,
Some("little") => ByteOrder::Little,
Some("big") => ByteOrder::Big,
@ -192,7 +128,7 @@ impl OdOptions {
}
};
let mut skip_bytes = match matches.opt_default("skip-bytes", "0") {
let mut skip_bytes = match matches.value_of(options::SKIP_BYTES) {
None => 0,
Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => i,
@ -223,8 +159,9 @@ impl OdOptions {
}
};
let mut line_bytes = match matches.opt_default("w", "32") {
let mut line_bytes = match matches.value_of(options::WIDTH) {
None => 16,
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
Some(s) => s.parse::<usize>().unwrap_or(0),
};
let min_bytes = formats.iter().fold(1, |max, next| {
@ -235,9 +172,9 @@ impl OdOptions {
line_bytes = min_bytes;
}
let output_duplicates = matches.opt_present("v");
let output_duplicates = matches.is_present(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.opt_str("read-bytes") {
let read_bytes = match matches.value_of(options::READ_BYTES) {
None => None,
Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => Some(i),
@ -247,10 +184,10 @@ impl OdOptions {
},
};
let radix = match matches.opt_str("A") {
let radix = match matches.value_of(options::ADDRESS_RADIX) {
None => Radix::Octal,
Some(s) => {
let st = s.into_bytes();
let st = s.as_bytes();
if st.len() != 1 {
return Err("Radix must be one of [d, o, n, x]".to_string());
} else {
@ -286,26 +223,244 @@ impl OdOptions {
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let opts = create_getopts_options();
let clap_opts = clap::App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(USAGE)
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ADDRESS_RADIX)
.short("A")
.long(options::ADDRESS_RADIX)
.help("Select the base in which file offsets are printed.")
.value_name("RADIX"),
)
.arg(
Arg::with_name(options::SKIP_BYTES)
.short("j")
.long(options::SKIP_BYTES)
.help("Skip bytes input bytes before formatting and writing.")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::READ_BYTES)
.short("N")
.long(options::READ_BYTES)
.help("limit dump to BYTES input bytes")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::ENDIAN)
.long(options::ENDIAN)
.help("byte order to use for multi-byte formats")
.possible_values(&["big", "little"])
.value_name("big|little"),
)
.arg(
Arg::with_name(options::STRINGS)
.short("S")
.long(options::STRINGS)
.help(
"output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.",
)
.default_value("3")
.value_name("BYTES"),
)
.arg(
Arg::with_name("a")
.short("a")
.help("named characters, ignoring high-order bit")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("b")
.short("b")
.help("octal bytes")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("c")
.short("c")
.help("ASCII characters or backslash escapes")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("d")
.short("d")
.help("unsigned decimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("D")
.short("D")
.help("unsigned decimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("o")
.short("o")
.help("octal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("I")
.short("I")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("L")
.short("L")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("i")
.short("i")
.help("decimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("l")
.short("l")
.help("decimal 8-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("x")
.short("x")
.help("hexadecimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("h")
.short("h")
.help("hexadecimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("O")
.short("O")
.help("octal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("s")
.short("s")
.help("decimal 2-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("X")
.short("X")
.help("hexadecimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("H")
.short("H")
.help("hexadecimal 4-byte units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("e")
.short("e")
.help("floating point double precision (64-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("f")
.short("f")
.help("floating point double precision (32-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name("F")
.short("F")
.help("floating point double precision (64-bit) units")
.multiple(true)
.takes_value(false),
)
.arg(
Arg::with_name(options::FORMAT)
.short("t")
.long(options::FORMAT)
.help("select output format or formats")
.multiple(true)
.value_name("TYPE"),
)
.arg(
Arg::with_name(options::OUTPUT_DUPLICATES)
.short("v")
.long(options::OUTPUT_DUPLICATES)
.help("do not use * to mark line suppression")
.takes_value(false)
.possible_values(&["big", "little"]),
)
.arg(
Arg::with_name(options::WIDTH)
.short("w")
.long(options::WIDTH)
.help(
"output BYTES bytes per output line. 32 is implied when BYTES is not \
specified.",
)
.default_value("32")
.value_name("BYTES"),
)
.arg(
Arg::with_name(options::VERSION)
.long(options::VERSION)
.help("output version information and exit.")
.takes_value(false),
)
.arg(
Arg::with_name(options::TRADITIONAL)
.long(options::TRADITIONAL)
.help("compatibility mode with one input, offset and label.")
.takes_value(false),
)
.arg(
Arg::with_name(options::FILENAME)
.hidden(true)
.multiple(true)
)
.settings(&[
AppSettings::TrailingVarArg,
AppSettings::DontDelimitTrailingValues,
AppSettings::DisableVersion,
AppSettings::DeriveDisplayOrder,
]);
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
show_usage_error!("{}", f);
return 1;
}
};
let clap_matches = clap_opts
.clone() // Clone to reuse clap_otps to print help
.get_matches_from(args.clone());
if matches.opt_present("help") {
println!("{}", opts.usage(&USAGE));
return 0;
}
if matches.opt_present("version") {
if clap_matches.is_present(options::VERSION) {
println!("{} {}", executable!(), VERSION);
return 0;
}
let od_options = match OdOptions::new(matches, args) {
let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => {
show_usage_error!("{}", s);
return 1;

View file

@ -1,20 +1,24 @@
use getopts::Matches;
use super::options;
use clap::ArgMatches;
/// Abstraction for getopts
pub trait CommandLineOpts {
/// returns all command line parameters which do not belong to an option.
fn inputs(&self) -> Vec<String>;
fn inputs(&self) -> Vec<&str>;
/// tests if any of the specified options is present.
fn opts_present(&self, _: &[&str]) -> bool;
}
/// Implementation for `getopts`
impl CommandLineOpts for Matches {
fn inputs(&self) -> Vec<String> {
self.free.clone()
impl<'a> CommandLineOpts for ArgMatches<'a> {
fn inputs(&self) -> Vec<&str> {
self.values_of(options::FILENAME)
.map(|values| values.collect())
.unwrap_or_default()
}
fn opts_present(&self, opts: &[&str]) -> bool {
self.opts_present(&opts.iter().map(|s| (*s).to_string()).collect::<Vec<_>>())
opts.iter().any(|opt| self.is_present(opt))
}
}
@ -39,7 +43,7 @@ pub enum CommandLineInputs {
/// '-' is used as filename if stdin is meant. This is also returned if
/// there is no input, as stdin is the default input.
pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs, String> {
let mut input_strings: Vec<String> = matches.inputs();
let mut input_strings = matches.inputs();
if matches.opts_present(&["traditional"]) {
return parse_inputs_traditional(input_strings);
@ -59,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
}
if input_strings.len() == 2 {
return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
n,
None,
)));
@ -69,23 +73,27 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
}
if input_strings.is_empty() {
input_strings.push("-".to_string());
input_strings.push("-");
}
Ok(CommandLineInputs::FileNames(input_strings))
Ok(CommandLineInputs::FileNames(
input_strings.iter().map(|s| s.to_string()).collect(),
))
}
/// interprets inputs when --traditional is on the command line
///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// it returns CommandLineInputs::FileNames (also to differentiate from the offset == 0)
pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLineInputs, String> {
pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineInputs, String> {
match input_strings.len() {
0 => Ok(CommandLineInputs::FileNames(vec!["-".to_string()])),
1 => {
let offset0 = parse_offset_operand(&input_strings[0]);
Ok(match offset0 {
Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
_ => CommandLineInputs::FileNames(input_strings),
_ => CommandLineInputs::FileNames(
input_strings.iter().map(|s| s.to_string()).collect(),
),
})
}
2 => {
@ -98,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
Some(m),
))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
m,
None,
))),
@ -110,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLin
let label = parse_offset_operand(&input_strings[2]);
match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone(),
input_strings[0].clone().to_owned(),
n,
Some(m),
))),
@ -178,8 +186,8 @@ mod tests {
}
impl<'a> CommandLineOpts for MockOptions<'a> {
fn inputs(&self) -> Vec<String> {
self.inputs.clone()
fn inputs(&self) -> Vec<&str> {
self.inputs.iter().map(|s| s.as_str()).collect()
}
fn opts_present(&self, opts: &[&str]) -> bool {
for expected in opts.iter() {