Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions argh/examples/help_text_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use argh::{FromArgs, TopLevelCommand};

#[derive(FromArgs)]
/// Defines a rectangle
#[argh(verbose_error, help_triggers("-h", "--help"))]
pub struct Rectangle {
#[argh(option, short = 'w')]
/// width; 23 if omitted
pub width: Option<u32>,

#[argh(option, short = 'h')]
/// height; 42 if omitted
pub height: Option<u32>,

#[argh(switch)]
/// print extended help and exit
pub long_help: bool,

#[argh(help_text)]
pub usage: Option<String>,
}

impl Rectangle {
fn check(&mut self) -> Result<(), String> {
if self.width.is_none() {
self.width = Some(23);
}
if self.height.is_none() {
self.height = Some(42);
}
let w64: u64 = self.width.unwrap().into();
let h64: u64 = self.height.unwrap().into();
let area = w64 * h64;
if area > 0xFFFFFFFF {
Err(String::from("You asked for too big a rectangle"))
} else {
return Ok(());
}
}
}

fn main() {
let mut rect: Rectangle = argh::from_env();
if let Err(msg) = rect.check() {
rect.report_error_and_exit(&msg)
}
if rect.long_help {
println!(
"{}\n\n{}",
rect.usage.unwrap(),
"Definition:
In Euclidean plane geometry, a rectangle is a quadrilateral with
four right angles. It can also be defined as: an equiangular
quadrilateral, since equiangular means that all of its angles are
equal (360°/4 = 90°); or a parallelogram containing a right angle.
A rectangle with four sides of equal length is a square. The term
“oblong” is used to refer to a non-square rectangle.

According to Wikipedia as of mid April 2024",
)
} else {
let w = rect.width.unwrap();
let h = rect.height.unwrap();
println!("Rectangle area is: {}={}x{}", w * h, w, h);
}
}
105 changes: 99 additions & 6 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,10 +603,95 @@ pub trait FromArgs: Sized {
fn redact_arg_values(_command_name: &[&str], _args: &[&str]) -> Result<Vec<String>, EarlyExit> {
Ok(vec!["<<REDACTED>>".into()])
}

#[doc(hidden)]
fn cook_help_text(_command_name: &[&str]) -> Option<String> {
None
}
}

/// A top-level `FromArgs` implementation that is not a subcommand.
pub trait TopLevelCommand: FromArgs {}
pub trait TopLevelCommand: FromArgs {
/// Prints error message and usage informaton to STDERR and exits with elevated
/// exit code. Handy when arguments combination needs to be checked for
/// consistency. Use together with ‘verbose_error’ attribute to garmonize internal
/// error reporting style.
///
/// # Example
///
/// ```rust
/// use argh::{FromArgs, TopLevelCommand};
///
/// #[derive(FromArgs)]
/// /// Defines a rectangle
/// #[argh(verbose_error, help_triggers("-h", "--help"))]
/// pub struct Rectangle {
/// #[argh(option, short = 'w')]
/// /// width; 23 if omitted
/// pub width: Option<u32>,
///
/// #[argh(option, short = 'h')]
/// /// height; 42 if omitted
/// pub height: Option<u32>,
///
/// #[argh(switch)]
/// /// print extended help and exit
/// pub long_help: bool,
///
/// #[argh(help_text)]
/// pub usage: Option<String>,
/// }
///
/// impl Rectangle {
/// fn check(&mut self) -> Result<(), String> {
/// if self.width.is_none() {
/// self.width = Some(23);
/// }
/// if self.height.is_none() {
/// self.height = Some(42);
/// }
/// let w64: u64 = self.width.unwrap().into();
/// let h64: u64 = self.height.unwrap().into();
/// let area = w64 * h64;
/// if area > 0xFFFFFFFF {
/// Err(String::from("You asked for too big a rectangle"))
/// } else {
/// return Ok(());
/// }
/// }
/// }
///
/// fn main() {
/// let mut rect: Rectangle = argh::from_env();
/// if let Err(msg) = rect.check() {
/// rect.report_error_and_exit(&msg)
/// }
/// if rect.long_help {
/// println!(
/// "{}\n\n{}",
/// rect.usage.unwrap(),
/// "Definition:
/// In Euclidean plane geometry, a rectangle is a quadrilateral with
/// four right angles. It can also be defined as: an equiangular
/// quadrilateral, since equiangular means that all of its angles are
/// equal (360°/4 = 90°); or a parallelogram containing a right angle.
/// A rectangle with four sides of equal length is a square. The term
/// “oblong” is used to refer to a non-square rectangle.
///
/// According to Wikipedia as of mid April 2024",
/// )
/// } else {
/// let w = rect.width.unwrap();
/// let h = rect.height.unwrap();
/// println!("Rectangle area is: {}={}x{}", w * h, w, h);
/// }
/// }
/// ```
fn report_error_and_exit(&self, msg: &str);

#[doc(hidden)]
fn cook_error_report(_bin_name: &str, msg: &str) -> String;
}

/// A `FromArgs` implementation that can parse into one or more subcommands.
pub trait SubCommands: FromArgs {
Expand Down Expand Up @@ -713,7 +798,7 @@ pub fn from_env<T: TopLevelCommand>() -> T {
0
}
Err(()) => {
eprintln!("{}\nRun {} --help for more information.", early_exit.output, cmd);
eprint!("{}", T::cook_error_report(cmd, &early_exit.output));
1
}
})
Expand All @@ -739,7 +824,7 @@ pub fn cargo_from_env<T: TopLevelCommand>() -> T {
0
}
Err(()) => {
eprintln!("{}\nRun --help for more information.", early_exit.output);
eprint!("{}", T::cook_error_report(cmd, &early_exit.output));
1
}
})
Expand Down Expand Up @@ -988,8 +1073,16 @@ impl<'a> ParseStructOptions<'a> {
.ok_or_else(|| ["No value provided for option '", arg, "'.\n"].concat())?;
*remaining_args = &remaining_args[1..];
pvs.fill_slot(arg, value).map_err(|s| {
["Error parsing option '", arg, "' with value '", value, "': ", &s, "\n"]
.concat()
[
"Cannot parse option '",
arg,
"' with value '",
value,
"': ",
&s,
"\n"
]
.concat()
})?;
}
}
Expand Down Expand Up @@ -1079,7 +1172,7 @@ impl<'a> ParseStructPositional<'a> {
fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> {
self.slot.fill_slot("", arg).map_err(|s| {
[
"Error parsing positional argument '",
"Cannot parse positional argument '",
self.name,
"' with value '",
arg,
Expand Down
56 changes: 53 additions & 3 deletions argh/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
clippy::unwrap_in_result
)]

use {argh::FromArgs, std::fmt::Debug};
use {argh::{FromArgs, TopLevelCommand}, std::fmt::Debug};

#[test]
fn basic_example() {
Expand Down Expand Up @@ -104,6 +104,56 @@ Options:
);
}

#[allow(dead_code)]
#[derive(FromArgs, Debug)]
/// Depth options
#[argh(verbose_error)]
struct Depth {
/// how deep the rabbit hole is
#[argh(option)]
depth: usize,

#[argh(help_text)]
help_text: Option<String>,
}

#[test]
fn help_text_pseudo_argument() {
let depth = Depth::from_args(&["cmdname"], &["--depth", "23"]).expect("failed at help text");
let help_text = depth.help_text.expect("None in place of help text");

assert_eq!(
help_text,
"Usage: cmdname --depth <depth>

Depth options

Options:
--depth how deep the rabbit hole is
--help, help display usage information
",
);
}

#[test]
fn verbose_error_report() {
// NB: Internal error messaages end with one LF.
let error_report = Depth::cook_error_report("cmdname", "<message>\n");
assert_eq!(
error_report,
"Error: <message>

Usage: cmdname --depth <depth>

Depth options

Options:
--depth how deep the rabbit hole is
--help, help display usage information
",
);
}

#[test]
fn nested_from_str_example() {
#[derive(FromArgs)]
Expand Down Expand Up @@ -474,7 +524,7 @@ mod options {
assert_output(&["-n", "5"], Parsed { n: 5 });
assert_error::<Parsed>(
&["-n", "x"],
r###"Error parsing option '-n' with value 'x': invalid digit found in string
r###"Cannot parse option '-n' with value 'x': invalid digit found in string
"###,
);
}
Expand Down Expand Up @@ -723,7 +773,7 @@ Options:
assert_output(&["5"], Parsed { n: 5 });
assert_error::<Parsed>(
&["x"],
r###"Error parsing positional argument 'n' with value 'x': invalid digit found in string
r###"Cannot parse positional argument 'n' with value 'x': invalid digit found in string
"###,
);
}
Expand Down
3 changes: 2 additions & 1 deletion argh_derive/src/args_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,8 @@ fn impl_args_info_data<'a>(
}
});
}
FieldKind::SubCommand => {}
FieldKind::SubCommand
| FieldKind::HelpText => {}
}
}

Expand Down
11 changes: 7 additions & 4 deletions argh_derive/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ pub(crate) fn help(

format_lit.push('\n');

quote! { {
quote! {
#subcommand_calculation
format!(#format_lit, command_name = #cmd_name_str_array_ident.join(" "), #subcommand_format_arg)
} }
Some(format!(#format_lit, command_name = #cmd_name_str_array_ident.join(" "), #subcommand_format_arg))
}
}

/// A section composed of exactly just the literals provided to the program.
Expand Down Expand Up @@ -190,7 +190,10 @@ fn option_usage(out: &mut String, field: &StructField<'_>) {
}

match field.kind {
FieldKind::SubCommand | FieldKind::Positional => unreachable!(), // don't have long_name
FieldKind::SubCommand
| FieldKind::HelpText
| FieldKind::Positional
=> unreachable!("subcommand, help_text and positional have no long names"),
FieldKind::Switch => {}
FieldKind::Option => {
out.push_str(" <");
Expand Down
Loading