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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,41 @@ struct SubCommandTwo {
}
```

## Synonyms

Synonyms can be specified for subcommands, options, and switches using the `synonyms` attribute.
These synonyms will be hidden from the help output but will be accepted during parsing.

```rust
use argh::FromArgs;

#[derive(FromArgs)]
/// A command with synonyms.
struct Cmd {
/// an option with synonyms
#[argh(option, synonyms = ["bar", "baz"])]
foo: String,

#[argh(subcommand)]
nested: MySubCommandEnum,
}

#[derive(FromArgs)]
#[argh(subcommand)]
enum MySubCommandEnum {
One(SubCommandOne),
}

#[derive(FromArgs)]
/// First subcommand.
#[argh(subcommand, name = "one", synonyms = ["uno", "eins"])]
struct SubCommandOne {
#[argh(option)]
/// how many x
x: usize,
}
```

NOTE: This is not an officially supported Google product.


Expand Down
90 changes: 77 additions & 13 deletions argh/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,41 @@
//! }
//! ```
//!
//! ## Synonyms
//!
//! Synonyms can be specified for subcommands, options, and switches using the `synonyms` attribute.
//! These synonyms will be hidden from the help output but will be accepted during parsing.
//!
//! ```rust
//! use argh::FromArgs;
//!
//! #[derive(FromArgs)]
//! /// A command with synonyms.
//! struct Cmd {
//! /// an option with synonyms
//! #[argh(option, synonyms = ["bar", "baz"])]
//! foo: String,
//!
//! #[argh(subcommand)]
//! nested: MySubCommandEnum,
//! }
//!
//! #[derive(FromArgs)]
//! #[argh(subcommand)]
//! enum MySubCommandEnum {
//! One(SubCommandOne),
//! }
//!
//! #[derive(FromArgs)]
//! /// First subcommand.
//! #[argh(subcommand, name = "one", synonyms = ["uno", "eins"])]
//! struct SubCommandOne {
//! #[argh(option)]
//! /// how many x
//! x: usize,
//! }
//! ```
//!
//! You can also discover subcommands dynamically at runtime. To do this,
//! declare subcommands as usual and add a variant to the enum with the
//! `dynamic` attribute. Instead of deriving `FromArgs`, the value inside the
Expand Down Expand Up @@ -649,6 +684,9 @@ pub trait SubCommands: FromArgs {
/// Info for the commands.
const COMMANDS: &'static [&'static CommandInfo];

/// Synonyms for the commands.
const COMMAND_SYNONYMS: &'static [(&'static str, &'static [&'static str])] = &[];

/// Get a list of commands that are discovered at runtime.
fn dynamic_commands() -> &'static [&'static CommandInfo] {
&[]
Expand All @@ -657,16 +695,23 @@ pub trait SubCommands: FromArgs {

impl<T: SubCommand> SubCommands for T {
const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];
const COMMAND_SYNONYMS: &'static [(&'static str, &'static [&'static str])] = &[
(T::COMMAND.name, T::SYNONYMS),
];
}

/// A `FromArgs` implementation that represents a single subcommand.
pub trait SubCommand: FromArgs {
/// Information about the subcommand.
const COMMAND: &'static CommandInfo;

/// Synonyms for the subcommand.
const SYNONYMS: &'static [&'static str] = &[];
}

impl<T: SubCommand> SubCommand for Box<T> {
const COMMAND: &'static CommandInfo = T::COMMAND;
const SYNONYMS: &'static [&'static str] = T::SYNONYMS;
}

/// Trait implemented by values returned from a dynamic subcommand handler.
Expand Down Expand Up @@ -1151,6 +1196,9 @@ pub struct ParseStructSubCommand<'a> {
// The subcommand commands
pub subcommands: &'static [&'static CommandInfo],

// The subcommand synonyms
pub command_synonyms: &'static [(&'static str, &'static [&'static str])],

pub dynamic_subcommands: &'a [&'static CommandInfo],

// The function to parse the subcommand arguments.
Expand All @@ -1170,24 +1218,40 @@ impl ParseStructSubCommand<'_> {
if subcommand.name == arg
|| arg.chars().count() == 1 && arg.chars().next().unwrap() == *subcommand.short
{
let mut command = cmd_name.to_owned();
command.push(subcommand.name);
let prepended_help;
let remaining_args = if help {
prepended_help = prepend_help(remaining_args);
&prepended_help
} else {
remaining_args
};

(self.parse_func)(&command, remaining_args)?;

return Ok(true);
return self.parse_subcommand(help, cmd_name, subcommand.name, remaining_args);
}
}

for (name, synonyms) in self.command_synonyms {
if synonyms.contains(&arg) {
return self.parse_subcommand(help, cmd_name, name, remaining_args);
}
}

Ok(false)
}

fn parse_subcommand(
&mut self,
help: bool,
cmd_name: &[&str],
subcommand_name: &str,
remaining_args: &[&str],
) -> Result<bool, EarlyExit> {
let mut command = cmd_name.to_owned();
command.push(subcommand_name);
let prepended_help;
let remaining_args = if help {
prepended_help = prepend_help(remaining_args);
&prepended_help
} else {
remaining_args
};

(self.parse_func)(&command, remaining_args)?;

Ok(true)
}
}

// Prepend `help` to a list of arguments.
Expand Down
2 changes: 1 addition & 1 deletion argh/tests/args_info_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ fn args_info_test_example() {
SubCommandInfo { name: "blow-up",
command: CommandInfoWithArgs { name: "blow-up",
short: &'\0',
description: "explosively separate",
description: "explosively separate",
flags:& [HELP_FLAG,
FlagInfo { kind: FlagInfoKind::Switch, optionality: Optionality::Optional, long: "--safely", short: None, description: "blow up bombs safely",
hidden:false }
Expand Down
77 changes: 77 additions & 0 deletions argh/tests/synonyms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#![cfg(feature = "help")]

use argh::FromArgs;

#[test]
fn test_subcommand_synonyms() {
#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
#[argh(subcommand)]
nested: MySubCommandEnum,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
One(SubCommandOne),
}

#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one", synonyms = ["uno", "eins"])]
struct SubCommandOne {
#[argh(option)]
/// how many x
x: usize,
}

let one = TopLevel::from_args(&["cmdname"], &["one", "--x", "2"]).expect("sc 1");
assert_eq!(one, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) });

let uno = TopLevel::from_args(&["cmdname"], &["uno", "--x", "2"]).expect("sc uno");
assert_eq!(uno, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) });

let eins = TopLevel::from_args(&["cmdname"], &["eins", "--x", "2"]).expect("sc eins");
assert_eq!(eins, TopLevel { nested: MySubCommandEnum::One(SubCommandOne { x: 2 }) });
}

#[test]
fn test_option_synonyms() {
#[derive(FromArgs, PartialEq, Debug)]
/// Command with option synonyms.
struct Cmd {
#[argh(option, long = "foo", synonyms = ["bar", "baz"])]
/// foo option
foo: String,
}

let a = Cmd::from_args(&["cmd"], &["--foo", "value"]).unwrap();
assert_eq!(a.foo, "value");

let b = Cmd::from_args(&["cmd"], &["--bar", "value"]).unwrap();
assert_eq!(b.foo, "value");

let c = Cmd::from_args(&["cmd"], &["--baz", "value"]).unwrap();
assert_eq!(c.foo, "value");
}

#[test]
fn test_switch_synonyms() {
#[derive(FromArgs, PartialEq, Debug)]
/// Command with switch synonyms.
struct Cmd {
#[argh(switch, long = "foo", synonyms = ["bar", "baz"])]
/// foo switch
foo: bool,
}

let a = Cmd::from_args(&["cmd"], &["--foo"]).unwrap();
assert!(a.foo);

let b = Cmd::from_args(&["cmd"], &["--bar"]).unwrap();
assert!(b.foo);

let c = Cmd::from_args(&["cmd"], &["--baz"]).unwrap();
assert!(c.foo);
}
2 changes: 1 addition & 1 deletion argh_derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"
syn = { version = "2.0", features = ["full"] }
argh_shared.workspace = true

[features]
Expand Down
Loading