diff --git a/Cargo.lock b/Cargo.lock index 8d4318dec..ad207edd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 14aea59a7..e4db9faf0 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -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" } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 47d3c29f8..791ddc4fc 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -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) -> Result { - let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) { + fn new<'a>(matches: ArgMatches<'a>, args: Vec) -> Result { + 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::().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; diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 89a833d94..915aa1d92 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -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; + 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 { - 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::>()) + 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 { - let mut input_strings: Vec = 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 Result) -> Result { +pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result { 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) -> Result 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) -> Result 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 { - 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() {