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]] [[package]]
name = "advapi32-sys" name = "advapi32-sys"
version = "0.2.0" version = "0.2.0"
@ -1868,7 +1866,7 @@ name = "uu_od"
version = "0.0.4" version = "0.0.4"
dependencies = [ dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "libc 0.2.85 (registry+https://github.com/rust-lang/crates.io-index)",
"uucore 0.0.7", "uucore 0.0.7",

View file

@ -16,7 +16,7 @@ path = "src/od.rs"
[dependencies] [dependencies]
byteorder = "1.3.2" byteorder = "1.3.2"
getopts = "0.2.18" clap = "2.33"
half = "1.6" half = "1.6"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } 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::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches};
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes 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 [OPTION]... [--] [FILENAME]...
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] 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 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 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 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 If an error occurred, a diagnostic message will be printed to stderr, and the
exitcode will be non-zero."#; exitcode will be non-zero."#;
fn create_getopts_options() -> getopts::Options { pub(crate) mod options {
let mut opts = getopts::Options::new(); pub const ADDRESS_RADIX: &str = "address-radix";
pub const SKIP_BYTES: &str = "skip-bytes";
opts.optopt( pub const READ_BYTES: &str = "read-bytes";
"A", pub const ENDIAN: &str = "endian";
"address-radix", pub const STRINGS: &str = "strings";
"Select the base in which file offsets are printed.", pub const FORMAT: &str = "format";
"RADIX", pub const OUTPUT_DUPLICATES: &str = "output-duplicates";
); pub const TRADITIONAL: &str = "traditional";
opts.optopt( pub const WIDTH: &str = "width";
"j", pub const VERSION: &str = "version";
"skip-bytes", pub const FILENAME: &str = "FILENAME";
"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
} }
struct OdOptions { struct OdOptions {
@ -182,8 +118,8 @@ struct OdOptions {
} }
impl OdOptions { impl OdOptions {
fn new(matches: getopts::Matches, args: Vec<String>) -> Result<OdOptions, String> { fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) { let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native, None => ByteOrder::Native,
Some("little") => ByteOrder::Little, Some("little") => ByteOrder::Little,
Some("big") => ByteOrder::Big, 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, None => 0,
Some(s) => match parse_number_of_bytes(&s) { Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => i, 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, None => 16,
Some(_) if matches.occurrences_of(options::WIDTH) == 0 => 16,
Some(s) => s.parse::<usize>().unwrap_or(0), Some(s) => s.parse::<usize>().unwrap_or(0),
}; };
let min_bytes = formats.iter().fold(1, |max, next| { let min_bytes = formats.iter().fold(1, |max, next| {
@ -235,9 +172,9 @@ impl OdOptions {
line_bytes = min_bytes; 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, None => None,
Some(s) => match parse_number_of_bytes(&s) { Some(s) => match parse_number_of_bytes(&s) {
Ok(i) => Some(i), 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, None => Radix::Octal,
Some(s) => { Some(s) => {
let st = s.into_bytes(); let st = s.as_bytes();
if st.len() != 1 { if st.len() != 1 {
return Err("Radix must be one of [d, o, n, x]".to_string()); return Err("Radix must be one of [d, o, n, x]".to_string());
} else { } else {
@ -286,26 +223,244 @@ impl OdOptions {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); 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..]) { let clap_matches = clap_opts
Ok(m) => m, .clone() // Clone to reuse clap_otps to print help
Err(f) => { .get_matches_from(args.clone());
show_usage_error!("{}", f);
return 1;
}
};
if matches.opt_present("help") { if clap_matches.is_present(options::VERSION) {
println!("{}", opts.usage(&USAGE));
return 0;
}
if matches.opt_present("version") {
println!("{} {}", executable!(), VERSION); println!("{} {}", executable!(), VERSION);
return 0; return 0;
} }
let od_options = match OdOptions::new(matches, args) { let od_options = match OdOptions::new(clap_matches, args) {
Err(s) => { Err(s) => {
show_usage_error!("{}", s); show_usage_error!("{}", s);
return 1; return 1;

View file

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