blob: b5cf5b928a93e0b09cb6bd8df8170e527784ea64 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Handles argument parsing.
//!
//! # Example
//!
//! ```
//! # use crosvm::argument::{Argument, Error, print_help, set_arguments};
//! # let args: std::slice::Iter<String> = [].iter();
//! let arguments = &[
//! Argument::positional("FILES", "files to operate on"),
//! Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
//! Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
//! Argument::flag("unmount", "Unmount the root"),
//! Argument::short_flag('h', "help", "Print help message."),
//! ];
//!
//! let match_res = set_arguments(args, arguments, |name, value| {
//! match name {
//! "" => println!("positional arg! {}", value.unwrap()),
//! "program" => println!("gonna use program {}", value.unwrap()),
//! "cpus" => {
//! let v: u32 = value.unwrap().parse().map_err(|_| {
//! Error::InvalidValue {
//! value: value.unwrap().to_owned(),
//! expected: String::from("this value for `cpus` needs to be integer"),
//! }
//! })?;
//! }
//! "unmount" => println!("gonna unmount"),
//! "help" => return Err(Error::PrintHelp),
//! _ => unreachable!(),
//! }
//! unreachable!();
//! });
//!
//! match match_res {
//! Ok(_) => println!("running with settings"),
//! Err(Error::PrintHelp) => print_help("best_program", "FILES", arguments),
//! Err(e) => println!("{}", e),
//! }
//! ```
use std::convert::TryFrom;
use std::result;
use std::str::FromStr;
use remain::sorted;
use terminal_size::{terminal_size, Width};
use thiserror::Error;
/// An error with argument parsing.
#[sorted]
#[derive(Error, Debug)]
pub enum Error {
/// Free error for use with the `serde_keyvalue` crate parser.
#[error("failed to parse key-value arguments: {0}")]
ConfigParserError(String),
/// The argument was required.
#[error("expected argument: {0}")]
ExpectedArgument(String),
/// The argument expects a value.
#[error("expected parameter value: {0}")]
ExpectedValue(String),
/// The argument's given value is invalid.
#[error("invalid value {value:?}: {expected}")]
InvalidValue { value: String, expected: String },
/// The help information was requested
#[error("help was requested")]
PrintHelp,
/// There was a syntax error with the argument.
#[error("syntax error: {0}")]
Syntax(String),
/// The argument was already given and none more are expected.
#[error("too many arguments: {0}")]
TooManyArguments(String),
/// The argument does not expect a value.
#[error("unexpected parameter value: {0}")]
UnexpectedValue(String),
/// The argument's name is unused.
#[error("unknown argument: {0}")]
UnknownArgument(String),
}
/// Result of a argument parsing.
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, PartialEq)]
pub enum ArgumentValueMode {
/// Specifies that an argument requires a value and that an error should be generated if
/// no value is provided during parsing.
Required,
/// Specifies that an argument does not allow a value and that an error should be returned
/// if a value is provided during parsing.
Disallowed,
/// Specifies that an argument may have a value during parsing but is not required to.
Optional,
}
/// Information about an argument expected from the command line.
///
/// # Examples
///
/// To indicate a flag style argument:
///
/// ```
/// # use crosvm::argument::Argument;
/// Argument::short_flag('f', "flag", "enable awesome mode");
/// ```
///
/// To indicate a parameter style argument that expects a value:
///
/// ```
/// # use crosvm::argument::Argument;
/// // "VALUE" and "NETMASK" are placeholder values displayed in the help message for these
/// // arguments.
/// Argument::short_value('v', "val", "VALUE", "how much do you value this usage information");
/// Argument::value("netmask", "NETMASK", "hides your netface");
/// ```
///
/// To indicate an argument with no short version:
///
/// ```
/// # use crosvm::argument::Argument;
/// Argument::flag("verbose", "this option is hard to type quickly");
/// ```
///
/// To indicate a positional argument:
///
/// ```
/// # use crosvm::argument::Argument;
/// Argument::positional("VALUES", "these are positional arguments");
/// ```
pub struct Argument {
/// The name of the value to display in the usage information.
pub value: Option<&'static str>,
/// Specifies how values should be handled for this this argument.
pub value_mode: ArgumentValueMode,
/// Optional single character shortened argument name.
pub short: Option<char>,
/// The long name of this argument.
pub long: &'static str,
/// Helpfuly usage information for this argument to display to the user.
pub help: &'static str,
}
impl Argument {
pub fn positional(value: &'static str, help: &'static str) -> Argument {
Argument {
value: Some(value),
value_mode: ArgumentValueMode::Required,
short: None,
long: "",
help,
}
}
pub fn value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
Argument {
value: Some(value),
value_mode: ArgumentValueMode::Required,
short: None,
long,
help,
}
}
pub fn short_value(
short: char,
long: &'static str,
value: &'static str,
help: &'static str,
) -> Argument {
Argument {
value: Some(value),
value_mode: ArgumentValueMode::Required,
short: Some(short),
long,
help,
}
}
pub fn flag(long: &'static str, help: &'static str) -> Argument {
Argument {
value: None,
value_mode: ArgumentValueMode::Disallowed,
short: None,
long,
help,
}
}
pub fn short_flag(short: char, long: &'static str, help: &'static str) -> Argument {
Argument {
value: None,
value_mode: ArgumentValueMode::Disallowed,
short: Some(short),
long,
help,
}
}
pub fn flag_or_value(long: &'static str, value: &'static str, help: &'static str) -> Argument {
Argument {
value: Some(value),
value_mode: ArgumentValueMode::Optional,
short: None,
long,
help,
}
}
}
fn parse_arguments<I, R, F>(args: I, mut f: F) -> Result<()>
where
I: Iterator<Item = R>,
R: AsRef<str>,
F: FnMut(&str, Option<&str>) -> Result<()>,
{
enum State {
// Initial state at the start and after finishing a single argument/value.
Top,
// The remaining arguments are all positional.
Positional,
// The next string is the value for the argument `name`.
Value { name: String },
}
let mut s = State::Top;
for arg in args {
let arg = arg.as_ref();
loop {
let mut arg_consumed = true;
s = match s {
State::Top => {
if arg == "--" {
State::Positional
} else if arg.starts_with("--") {
let param = arg.trim_start_matches('-');
if param.contains('=') {
let mut iter = param.splitn(2, '=');
let name = iter.next().unwrap();
let value = iter.next().unwrap();
if name.is_empty() {
return Err(Error::Syntax(
"expected parameter name before `=`".to_owned(),
));
}
if value.is_empty() {
return Err(Error::Syntax(
"expected parameter value after `=`".to_owned(),
));
}
f(name, Some(value))?;
State::Top
} else {
State::Value {
name: param.to_owned(),
}
}
} else if arg.starts_with('-') {
if arg.len() == 1 {
return Err(Error::Syntax(
"expected argument short name after `-`".to_owned(),
));
}
let name = &arg[1..2];
let value = if arg.len() > 2 { Some(&arg[2..]) } else { None };
if let Err(e) = f(name, value) {
if let Error::ExpectedValue(_) = e {
State::Value {
name: name.to_owned(),
}
} else {
return Err(e);
}
} else {
State::Top
}
} else {
f("", Some(arg))?;
State::Positional
}
}
State::Positional => {
f("", Some(arg))?;
State::Positional
}
State::Value { name } => {
if arg.starts_with('-') {
arg_consumed = false;
f(&name, None)?;
} else if let Err(e) = f(&name, Some(arg)) {
arg_consumed = false;
f(&name, None).map_err(|_| e)?;
}
State::Top
}
};
if arg_consumed {
break;
}
}
}
// If we ran out of arguments while parsing the last parameter, which may be either a
// value parameter or a flag, try to parse it as a flag. This will produce "missing value"
// error if the parameter is in fact a value parameter, which is the desired outcome.
match s {
State::Value { name } => f(&name, None),
_ => Ok(()),
}
}
/// Parses the given `args` against the list of know arguments `arg_list` and calls `f` with each
/// present argument and value if required.
///
/// This function guarantees that only valid long argument names from `arg_list` are sent to the
/// callback `f`. It is also guaranteed that if an arg requires a value (i.e.
/// `arg.value.is_some()`), the value will be `Some` in the callbacks arguments. If the callback
/// returns `Err`, this function will end parsing and return that `Err`.
///
/// See the [module level](index.html) example for a usage example.
pub fn set_arguments<I, R, F>(args: I, arg_list: &[Argument], mut f: F) -> Result<()>
where
I: Iterator<Item = R>,
R: AsRef<str>,
F: FnMut(&str, Option<&str>) -> Result<()>,
{
parse_arguments(args, |name, value| {
let mut matches = None;
for arg in arg_list {
if let Some(short) = arg.short {
if name.len() == 1 && name.starts_with(short) {
if value.is_some() != arg.value.is_some() {
return Err(Error::ExpectedValue(short.to_string()));
}
matches = Some(arg.long);
}
}
if matches.is_none() && arg.long == name {
if value.is_none() && arg.value_mode == ArgumentValueMode::Required {
return Err(Error::ExpectedValue(arg.long.to_owned()));
}
if value.is_some() && arg.value_mode == ArgumentValueMode::Disallowed {
return Err(Error::UnexpectedValue(arg.long.to_owned()));
}
matches = Some(arg.long);
}
}
match matches {
Some(long) => f(long, value),
None => Err(Error::UnknownArgument(name.to_owned())),
}
})
}
const DEFAULT_COLUMNS: usize = 80;
/// Get the number of columns on a display, with a reasonable default.
fn get_columns() -> usize {
if let Some((Width(columns), _)) = terminal_size() {
return columns.into();
}
DEFAULT_COLUMNS
}
/// Poor man's reflowing function for string. This function will unsplit the
/// lines, an empty line splits a paragraph.
fn reflow(s: &str, offset: usize, width: usize) -> String {
let mut lines: Vec<String> = vec![];
let mut prev = "";
let filler = " ".repeat(offset);
for line in s.lines() {
let line = line.trim();
if line.is_empty() {
// Skip the empty line, the paragraph delimiter.
} else if prev.is_empty() {
// Start a new paragraph if the previous line was empty.
lines.push(line.to_string());
} else if let Some(last) = lines.last_mut() {
*last += " ";
*last += line;
}
prev = line;
}
let mut lines = lines.into_iter().flat_map(|line| {
let mut output = vec![];
// Split the line with the last space found, or if the word exceeds
// length of one line, give up and use the full width.
let mut line = line.as_str();
while let Some(s) = line.get(0..width) {
let offset = s.rfind(" ").unwrap_or(s.len());
output.push(s[0..offset].to_string());
line = &line[offset + 1..];
}
// Here we should have the remaining part of the line that is less
// than `width`.
output.push(line.to_string());
output
});
match lines.next() {
None => String::new(),
Some(line) => std::iter::once(line)
.chain(lines.map(|line| filler.clone() + &line))
.collect::<Vec<_>>()
.join("\n"),
}
}
/// Obtain the leading part of the help message. The output is later processed
/// to reflow. Depending on how short this, newline is used.
fn get_leading_part(arg: &Argument) -> String {
[
match arg.short {
Some(s) => format!(" -{}, ", s),
None => " ".to_string(),
},
if arg.long.is_empty() {
" ".to_string()
} else {
"--".to_string()
},
format!("{:<12}", arg.long),
if let Some(v) = arg.value {
format!("{}{:<9} ", if arg.long.is_empty() { " " } else { "=" }, v)
} else {
" ".to_string()
},
]
.join("")
}
/// Prints command line usage information to stdout.
///
/// Usage information is printed according to the help fields in `args` with a leading usage line.
/// The usage line is of the format "`program_name` \[ARGUMENTS\] `required_arg`".
pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
println!(
"Usage: {} {}{}\n",
program_name,
if args.is_empty() { "" } else { "[ARGUMENTS] " },
required_arg
);
if args.is_empty() {
return;
}
let indent_depth = 30;
let minimum_width_of_help = DEFAULT_COLUMNS - 1 - indent_depth;
let columns = get_columns();
let columns = minimum_width_of_help.max(columns - indent_depth);
println!("Argument{}:", if args.len() > 1 { "s" } else { "" });
for arg in args {
let leading_part = get_leading_part(arg);
if leading_part.len() <= indent_depth {
print!(
"{}{}",
leading_part,
" ".repeat(indent_depth - leading_part.len())
);
} else {
print!("{}\n{}", leading_part, " ".repeat(indent_depth));
}
println!("{}", reflow(arg.help, indent_depth, columns));
}
}
pub fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64> {
// Parse string starting with 0x as hex and others as numbers.
if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
u64::from_str_radix(hex_string, 16)
} else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
u64::from_str_radix(hex_string, 16)
} else {
u64::from_str(maybe_hex_string)
}
.map_err(|e| Error::InvalidValue {
value: maybe_hex_string.to_string(),
expected: e.to_string(),
})
}
pub struct KeyValuePair<'a> {
context: &'a str,
key: &'a str,
value: Option<&'a str>,
}
impl<'a> KeyValuePair<'a> {
fn handle_parse_err<T, E: std::error::Error>(
&self,
result: std::result::Result<T, E>,
) -> Result<T> {
result.map_err(|e| {
self.invalid_value_err(format!(
"Failed to parse parameter `{}` as {}: {}",
self.key,
std::any::type_name::<T>(),
e
))
})
}
pub fn key(&self) -> &'a str {
self.key
}
pub fn value(&self) -> Result<&'a str> {
self.value.ok_or(Error::ExpectedValue(format!(
"{}: parameter `{}` requires a value",
self.context, self.key
)))
}
fn get_numeric<T>(&self, val: &str) -> Result<T>
where
T: TryFrom<u64>,
<T as TryFrom<u64>>::Error: std::error::Error,
{
let num = parse_hex_or_decimal(val)?;
self.handle_parse_err(T::try_from(num))
}
pub fn parse_numeric<T>(&self) -> Result<T>
where
T: TryFrom<u64>,
<T as TryFrom<u64>>::Error: std::error::Error,
{
let val = self.value()?;
self.get_numeric(val)
}
pub fn key_numeric<T>(&self) -> Result<T>
where
T: TryFrom<u64>,
<T as TryFrom<u64>>::Error: std::error::Error,
{
self.get_numeric(self.key())
}
pub fn parse<T>(&self) -> Result<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error,
{
self.handle_parse_err(T::from_str(self.value()?))
}
pub fn parse_or<T>(&self, default: T) -> Result<T>
where
T: FromStr,
<T as FromStr>::Err: std::error::Error,
{
match self.value {
Some(v) => self.handle_parse_err(T::from_str(v)),
None => Ok(default),
}
}
pub fn invalid_key_err(&self) -> Error {
Error::UnknownArgument(format!(
"{}: Unknown parameter `{}`",
self.context, self.key
))
}
pub fn invalid_value_err(&self, description: String) -> Error {
Error::InvalidValue {
value: self
.value
.expect("invalid value error without value")
.to_string(),
expected: format!("{}: {}", self.context, description),
}
}
}
/// Parse a string of delimiter-separated key-value options. This is intended to simplify parsing
/// of command-line options that take a bunch of parameters encoded into the argument, e.g. for
/// setting up an emulated hardware device. Returns an Iterator of KeyValuePair, which provides
/// convenience functions to parse numeric values and performs appropriate error handling.
///
/// `flagname` - name of the command line parameter, used as context in error messages
/// `s` - the string to parse
/// `delimiter` - the character that separates individual pairs
///
/// Usage example:
/// ```
/// # use crosvm::argument::{Result, parse_key_value_options};
///
/// fn parse_turbo_button_parameters(s: &str) -> Result<(String, u32, bool)> {
/// let mut color = String::new();
/// let mut speed = 0u32;
/// let mut turbo = false;
///
/// for opt in parse_key_value_options("turbo-button", s, ',') {
/// match opt.key() {
/// "color" => color = opt.value()?.to_string(),
/// "speed" => speed = opt.parse_numeric::<u32>()?,
/// "turbo" => turbo = opt.parse_or::<bool>(true)?,
/// _ => return Err(opt.invalid_key_err()),
/// }
/// }
///
/// Ok((color, speed, turbo))
/// }
///
/// assert_eq!(parse_turbo_button_parameters("color=red,speed=0xff,turbo").unwrap(),
/// ("red".to_string(), 0xff, true))
/// ```
///
/// TODO: upgrade `delimiter` to generic Pattern support once that has been stabilized
/// at <https://github.com/rust-lang/rust/issues/27721>.
pub fn parse_key_value_options<'a>(
flagname: &'a str,
s: &'a str,
delimiter: char,
) -> impl Iterator<Item = KeyValuePair<'a>> {
s.split(delimiter)
.map(|frag| frag.splitn(2, '='))
.map(move |mut kv| KeyValuePair {
context: flagname,
key: kv.next().unwrap_or(""),
value: kv.next(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_help() {
let arguments = [Argument::short_flag('h', "help", "Print help message.")];
let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| match name {
"help" => Err(Error::PrintHelp),
_ => unreachable!(),
});
match match_res {
Err(Error::PrintHelp) => {}
_ => unreachable!(),
}
}
#[test]
fn mixed_args() {
let arguments = [
Argument::positional("FILES", "files to operate on"),
Argument::short_value('p', "program", "PROGRAM", "Program to apply to each file"),
Argument::short_value('c', "cpus", "N", "Number of CPUs to use. (default: 1)"),
Argument::flag("unmount", "Unmount the root"),
Argument::short_flag('h', "help", "Print help message."),
];
let mut unmount = false;
let match_res = set_arguments(
["--cpus", "3", "--program", "hello", "--unmount", "file"].iter(),
&arguments[..],
|name, value| {
match name {
"" => assert_eq!(value.unwrap(), "file"),
"program" => assert_eq!(value.unwrap(), "hello"),
"cpus" => {
let c: u32 = value.unwrap().parse().map_err(|_| Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("this value for `cpus` needs to be integer"),
})?;
assert_eq!(c, 3);
}
"unmount" => unmount = true,
"help" => return Err(Error::PrintHelp),
_ => unreachable!(),
};
Ok(())
},
);
assert!(match_res.is_ok());
assert!(unmount);
}
#[test]
fn name_value_pair() {
let arguments = [Argument::short_value(
'c',
"cpus",
"N",
"Number of CPUs to use. (default: 1)",
)];
let match_res = set_arguments(
["-c", "5", "--cpus", "5", "-c5", "--cpus=5"].iter(),
&arguments[..],
|name, value| {
assert_eq!(name, "cpus");
assert_eq!(value, Some("5"));
Ok(())
},
);
assert!(match_res.is_ok());
let not_match_res = set_arguments(
["-c", "5", "--cpus"].iter(),
&arguments[..],
|name, value| {
assert_eq!(name, "cpus");
assert_eq!(value, Some("5"));
Ok(())
},
);
assert!(not_match_res.is_err());
}
#[test]
fn flag_or_value() {
let run_case = |args| -> Option<String> {
let arguments = [
Argument::positional("FILES", "files to operate on"),
Argument::flag_or_value("gpu", "[2D|3D]", "Enable or configure gpu"),
Argument::flag("foo", "Enable foo."),
Argument::value("bar", "stuff", "Configure bar."),
];
let mut gpu_value: Option<String> = None;
let match_res =
set_arguments(args, &arguments[..], |name: &str, value: Option<&str>| {
match name {
"" => assert_eq!(value.unwrap(), "file1"),
"foo" => assert!(value.is_none()),
"bar" => assert_eq!(value.unwrap(), "stuff"),
"gpu" => match value {
Some(v) => match v {
"2D" | "3D" => {
gpu_value = Some(v.to_string());
}
_ => {
return Err(Error::InvalidValue {
value: v.to_string(),
expected: String::from("2D or 3D"),
})
}
},
None => {
gpu_value = None;
}
},
_ => unreachable!(),
};
Ok(())
});
assert!(match_res.is_ok());
gpu_value
};
// Used as flag and followed by positional
assert_eq!(run_case(["--gpu", "file1"].iter()), None);
// Used as flag and followed by flag
assert_eq!(run_case(["--gpu", "--foo", "file1",].iter()), None);
// Used as flag and followed by value
assert_eq!(run_case(["--gpu", "--bar=stuff", "file1"].iter()), None);
// Used as value and followed by positional
assert_eq!(run_case(["--gpu=2D", "file1"].iter()).unwrap(), "2D");
// Used as value and followed by flag
assert_eq!(run_case(["--gpu=2D", "--foo"].iter()).unwrap(), "2D");
// Used as value and followed by value
assert_eq!(
run_case(["--gpu=2D", "--bar=stuff", "file1"].iter()).unwrap(),
"2D"
);
}
#[test]
fn parse_key_value_options_simple() {
let mut opts = parse_key_value_options("test", "fruit=apple,number=13,flag,hex=0x123", ',');
let kv1 = opts.next().unwrap();
assert_eq!(kv1.key(), "fruit");
assert_eq!(kv1.value().unwrap(), "apple");
let kv2 = opts.next().unwrap();
assert_eq!(kv2.key(), "number");
assert_eq!(kv2.parse::<u32>().unwrap(), 13);
let kv3 = opts.next().unwrap();
assert_eq!(kv3.key(), "flag");
assert!(kv3.value().is_err());
assert!(kv3.parse_or::<bool>(true).unwrap());
let kv4 = opts.next().unwrap();
assert_eq!(kv4.key(), "hex");
assert_eq!(kv4.parse_numeric::<u32>().unwrap(), 0x123);
assert!(opts.next().is_none());
}
#[test]
fn parse_key_value_options_overflow() {
let mut opts = parse_key_value_options("test", "key=1000000000000000", ',');
let kv = opts.next().unwrap();
assert!(kv.parse::<u32>().is_err());
assert!(kv.parse_numeric::<u32>().is_err());
}
#[test]
fn parse_hex_or_decimal_simple() {
assert_eq!(parse_hex_or_decimal("15").unwrap(), 15);
assert_eq!(parse_hex_or_decimal("0x15").unwrap(), 0x15);
assert_eq!(parse_hex_or_decimal("0X15").unwrap(), 0x15);
assert!(parse_hex_or_decimal("0xz").is_err());
assert!(parse_hex_or_decimal("hello world").is_err());
}
#[test]
fn parse_key_value_options_numeric_key() {
let mut opts = parse_key_value_options("test", "0x30,0x100=value,nonnumeric=value", ',');
let kv = opts.next().unwrap();
assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x30);
let kv = opts.next().unwrap();
assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x100);
assert_eq!(kv.value().unwrap(), "value");
let kv = opts.next().unwrap();
assert!(kv.key_numeric::<u32>().is_err());
assert_eq!(kv.key(), "nonnumeric");
}
#[test]
fn reflow_simple() {
assert_eq!(reflow("Hello world, this is a sample of reflowing operation that should work generally. However I don't know if it is useful", 10, 40),
"Hello world, this is a sample of
reflowing operation that should work
generally. However I don't know if it
is useful");
}
#[test]
fn reflow_paragraph() {
assert_eq!(
reflow(
"Hello world, this is a sample of reflowing operation that should work generally.
I am going to give you another paragraph. However I don't know if it is useful",
10,
40
),
"Hello world, this is a sample of
reflowing operation that should work
generally.
I am going to give you another
paragraph. However I don't know if it
is useful"
);
}
#[test]
fn get_leading_part_short() {
assert_eq!(
get_leading_part(&Argument::positional("FILES", "files to operate on")).len(),
30
);
assert_eq!(
get_leading_part(&Argument::flag_or_value(
"gpu",
"[2D|3D]",
"Enable or configure gpu"
))
.len(),
30
);
assert_eq!(
get_leading_part(&Argument::flag("foo", "Enable foo.")).len(),
20
);
assert_eq!(
get_leading_part(&Argument::value("bar", "stuff", "Configure bar.")).len(),
30
);
}
#[test]
fn get_leading_part_long() {
assert_eq!(
get_leading_part(&Argument::value(
"very-long-flag-name",
"stuff",
"Configure bar."
))
.len(),
37
);
}
}