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:
parent
f60790dd41
commit
ca8fbc37bf
4 changed files with 284 additions and 123 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue