From fb5bb82c216c38b7a536b21f6a7bd2b415efedde Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Mon, 27 Nov 2023 20:19:03 +0100 Subject: [PATCH 1/6] add an `alias` keyword to the parser --- crates/xflags-macros/src/ast.rs | 1 + crates/xflags-macros/src/emit.rs | 9 +++++++-- crates/xflags-macros/src/parse.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/xflags-macros/src/ast.rs b/crates/xflags-macros/src/ast.rs index 7fb9df1..9f855c4 100644 --- a/crates/xflags-macros/src/ast.rs +++ b/crates/xflags-macros/src/ast.rs @@ -13,6 +13,7 @@ impl XFlags { #[derive(Debug)] pub(crate) struct Cmd { pub(crate) name: String, + pub(crate) aliases: Vec, pub(crate) doc: Option, pub(crate) args: Vec, pub(crate) flags: Vec, diff --git a/crates/xflags-macros/src/emit.rs b/crates/xflags-macros/src/emit.rs index 1f3d88e..b942fe0 100644 --- a/crates/xflags-macros/src/emit.rs +++ b/crates/xflags-macros/src/emit.rs @@ -242,7 +242,8 @@ fn emit_match_flag_rec(buf: &mut String, prefix: &mut String, cmd: &ast::Cmd) { fn emit_match_arg_rec(buf: &mut String, prefix: &mut String, cmd: &ast::Cmd) { for sub in cmd.named_subcommands() { - w!(buf, "({}, \"{}\") => state_ = {},\n", cmd.idx, sub.name, sub.idx); + let sub_match = sub.all_identifiers().map(|s| format!("\"{s}\"")).collect::>().join(" | "); + w!(buf, "({}, {}) => state_ = {},\n", cmd.idx, sub_match, sub.idx); } if !cmd.args.is_empty() || cmd.has_subcommands() { w!(buf, "({}, _) => {{\n", cmd.idx); @@ -406,7 +407,8 @@ fn help_rec(buf: &mut String, prefix: &str, cmd: &ast::Cmd) { let mut empty_help = true; if !cmd.name.is_empty() { empty_help = false; - w!(buf, "{}{}\n", prefix, cmd.name); + let idens = cmd.all_identifiers().cloned().collect::>().join(" | "); + w!(buf, "{}{}\n", prefix, idens); } if let Some(doc) = &cmd.doc { empty_help = false; @@ -483,6 +485,9 @@ impl ast::Cmd { } camel(&self.name) } + pub(crate) fn all_identifiers(&self) -> impl Iterator { + [&self.name].into_iter().chain(self.aliases.iter()) + } fn cmd_enum_ident(&self) -> String { format!("{}Cmd", self.ident()) } diff --git a/crates/xflags-macros/src/parse.rs b/crates/xflags-macros/src/parse.rs index 48f9588..88a0dd7 100644 --- a/crates/xflags-macros/src/parse.rs +++ b/crates/xflags-macros/src/parse.rs @@ -82,11 +82,18 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result { cmd_name(p)? }; + let aliases = if p.eat_keyword("alias") { + alias_names(p)? + } else { + vec![] + }; + let idx = p.idx; p.idx += 1; let mut res = ast::Cmd { name, + aliases, doc: None, args: Vec::new(), flags: Vec::new(), @@ -135,6 +142,15 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result { if !anon { p.exit_delim()?; } + + let mut unique_identifiers = std::collections::HashSet::new(); + + for ident in res.subcommands.iter().map(|cmd| cmd.all_identifiers()).flatten() { + if !unique_identifiers.insert(ident) { + bail!("`{}` is defined multiple times", ident) + } + } + Ok(res) } @@ -242,6 +258,16 @@ fn cmd_name(p: &mut Parser) -> Result { Ok(name) } +fn alias_names(p: &mut Parser) -> Result> { + let mut aliases = vec![p.expect_name()?]; + + while let Some(alias) = p.eat_name() { + aliases.push(alias); + } + + Ok(aliases) +} + fn flag_name(p: &mut Parser) -> Result { let name = p.expect_name()?; if !name.starts_with('-') { From e331324933c52402dd6eb79700b6a06cd08e13d5 Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Mon, 27 Nov 2023 20:19:08 +0100 Subject: [PATCH 2/6] update documentation --- crates/xflags/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/xflags/src/lib.rs b/crates/xflags/src/lib.rs index 0eda9de..a8feddf 100644 --- a/crates/xflags/src/lib.rs +++ b/crates/xflags/src/lib.rs @@ -162,6 +162,14 @@ //! } //! ``` //! +//! You can create aliases if desired. In this case, `run` can be called as `run`, `r` and `exec`: +//! +//! ```rust +//! xflags::xflags! { +//! cmd run alias r exec {} +//! } +//! ``` +//! //! Nesting **cmd** is allowed. `xflag` automatically generates boilerplate //! enums for subcommands: //! From 53e8d09cca28236373ae15f2c8931d6ed33fbaf6 Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Tue, 28 Nov 2023 17:00:41 +0100 Subject: [PATCH 3/6] ran `cargo fmt` --- crates/xflags-macros/src/emit.rs | 3 ++- crates/xflags-macros/src/parse.rs | 2 +- crates/xflags/src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/xflags-macros/src/emit.rs b/crates/xflags-macros/src/emit.rs index b942fe0..d217eee 100644 --- a/crates/xflags-macros/src/emit.rs +++ b/crates/xflags-macros/src/emit.rs @@ -242,7 +242,8 @@ fn emit_match_flag_rec(buf: &mut String, prefix: &mut String, cmd: &ast::Cmd) { fn emit_match_arg_rec(buf: &mut String, prefix: &mut String, cmd: &ast::Cmd) { for sub in cmd.named_subcommands() { - let sub_match = sub.all_identifiers().map(|s| format!("\"{s}\"")).collect::>().join(" | "); + let sub_match = + sub.all_identifiers().map(|s| format!("\"{s}\"")).collect::>().join(" | "); w!(buf, "({}, {}) => state_ = {},\n", cmd.idx, sub_match, sub.idx); } if !cmd.args.is_empty() || cmd.has_subcommands() { diff --git a/crates/xflags-macros/src/parse.rs b/crates/xflags-macros/src/parse.rs index 88a0dd7..e239b59 100644 --- a/crates/xflags-macros/src/parse.rs +++ b/crates/xflags-macros/src/parse.rs @@ -144,7 +144,7 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result { } let mut unique_identifiers = std::collections::HashSet::new(); - + for ident in res.subcommands.iter().map(|cmd| cmd.all_identifiers()).flatten() { if !unique_identifiers.insert(ident) { bail!("`{}` is defined multiple times", ident) diff --git a/crates/xflags/src/lib.rs b/crates/xflags/src/lib.rs index a8feddf..37153bb 100644 --- a/crates/xflags/src/lib.rs +++ b/crates/xflags/src/lib.rs @@ -163,13 +163,13 @@ //! ``` //! //! You can create aliases if desired. In this case, `run` can be called as `run`, `r` and `exec`: -//! +//! //! ```rust //! xflags::xflags! { //! cmd run alias r exec {} //! } //! ``` -//! +//! //! Nesting **cmd** is allowed. `xflag` automatically generates boilerplate //! enums for subcommands: //! From eae9ca3828b31f5f9c8f91b6bfcaa37dd91687e8 Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Tue, 28 Nov 2023 17:01:13 +0100 Subject: [PATCH 4/6] remove need for `alias` keyword --- crates/xflags-macros/src/parse.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/xflags-macros/src/parse.rs b/crates/xflags-macros/src/parse.rs index e239b59..e19c1a4 100644 --- a/crates/xflags-macros/src/parse.rs +++ b/crates/xflags-macros/src/parse.rs @@ -82,11 +82,7 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result { cmd_name(p)? }; - let aliases = if p.eat_keyword("alias") { - alias_names(p)? - } else { - vec![] - }; + let aliases = alias_names(p); let idx = p.idx; p.idx += 1; @@ -258,14 +254,14 @@ fn cmd_name(p: &mut Parser) -> Result { Ok(name) } -fn alias_names(p: &mut Parser) -> Result> { - let mut aliases = vec![p.expect_name()?]; +fn alias_names(p: &mut Parser) -> Vec { + let mut aliases = vec![]; while let Some(alias) = p.eat_name() { aliases.push(alias); } - Ok(aliases) + aliases } fn flag_name(p: &mut Parser) -> Result { From 126e5a9d04f576321ef0da48c6ac70d768673e24 Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Tue, 28 Nov 2023 17:01:33 +0100 Subject: [PATCH 5/6] add test for aliases --- crates/xflags-macros/tests/data/aliases.rs | 11 +++ crates/xflags-macros/tests/it/aliases.rs | 109 +++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 crates/xflags-macros/tests/data/aliases.rs create mode 100644 crates/xflags-macros/tests/it/aliases.rs diff --git a/crates/xflags-macros/tests/data/aliases.rs b/crates/xflags-macros/tests/data/aliases.rs new file mode 100644 index 0000000..e3ee798 --- /dev/null +++ b/crates/xflags-macros/tests/data/aliases.rs @@ -0,0 +1,11 @@ +xflags! { + /// commands with different aliases + cmd alias-cmd { + /// And even an aliased subcommand! + cmd sub s { + /// Little sanity check to see if this still works as intended + optional -c, --count count: usize + } + cmd this one has a lot of aliases {} + } +} diff --git a/crates/xflags-macros/tests/it/aliases.rs b/crates/xflags-macros/tests/it/aliases.rs new file mode 100644 index 0000000..7c665d1 --- /dev/null +++ b/crates/xflags-macros/tests/it/aliases.rs @@ -0,0 +1,109 @@ +#![allow(unused)] +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Debug)] +pub struct AliasCmd { + pub subcommand: AliasCmdCmd, +} + +#[derive(Debug)] +pub enum AliasCmdCmd { + Sub(Sub), + This(This), +} + +#[derive(Debug)] +pub struct Sub { + pub count: Option, +} + +#[derive(Debug)] +pub struct This; + +impl AliasCmd { + #[allow(dead_code)] + pub fn from_env_or_exit() -> Self { + Self::from_env_or_exit_() + } + + #[allow(dead_code)] + pub fn from_env() -> xflags::Result { + Self::from_env_() + } + + #[allow(dead_code)] + pub fn from_vec(args: Vec) -> xflags::Result { + Self::from_vec_(args) + } +} + +impl AliasCmd { + fn from_env_or_exit_() -> Self { + Self::from_env_().unwrap_or_else(|err| err.exit()) + } + fn from_env_() -> xflags::Result { + let mut p = xflags::rt::Parser::new_from_env(); + Self::parse_(&mut p) + } + fn from_vec_(args: Vec) -> xflags::Result { + let mut p = xflags::rt::Parser::new(args); + Self::parse_(&mut p) + } +} + +impl AliasCmd { + fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result { + #![allow(non_snake_case)] + let mut sub__count = Vec::new(); + + let mut state_ = 0u8; + while let Some(arg_) = p_.pop_flag() { + match arg_ { + Ok(flag_) => match (state_, flag_.as_str()) { + (0 | 1 | 2, "--help" | "-h") => return Err(p_.help(Self::HELP_)), + (1, "--count" | "-c") => { + sub__count.push(p_.next_value_from_str::(&flag_)?) + } + _ => return Err(p_.unexpected_flag(&flag_)), + }, + Err(arg_) => match (state_, arg_.to_str().unwrap_or("")) { + (0, "sub" | "s") => state_ = 1, + (0, "this" | "one" | "has" | "a" | "lot" | "of" | "aliases") => state_ = 2, + (0, _) => { + return Err(p_.unexpected_arg(arg_)); + } + _ => return Err(p_.unexpected_arg(arg_)), + }, + } + } + Ok(AliasCmd { + subcommand: match state_ { + 1 => AliasCmdCmd::Sub(Sub { count: p_.optional("--count", sub__count)? }), + 2 => AliasCmdCmd::This(This {}), + _ => return Err(p_.subcommand_required()), + }, + }) + } +} +impl AliasCmd { + const HELP_: &'static str = "\ +alias-cmd + commands with different aliases + +OPTIONS: + -h, --help + Prints help information. + +SUBCOMMANDS: + +alias-cmd sub | s + And even an aliased subcommand! + + OPTIONS: + -c, --count + Little sanity check to see if this still works as intended + + +alias-cmd this | one | has | a | lot | of | aliases +"; +} From 5ab814694131ebd89e7722a8e954a818dd7daf81 Mon Sep 17 00:00:00 2001 From: "oxey:yum" Date: Tue, 28 Nov 2023 17:19:58 +0100 Subject: [PATCH 6/6] update docs --- crates/xflags/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/xflags/src/lib.rs b/crates/xflags/src/lib.rs index 37153bb..ad15026 100644 --- a/crates/xflags/src/lib.rs +++ b/crates/xflags/src/lib.rs @@ -162,11 +162,12 @@ //! } //! ``` //! -//! You can create aliases if desired. In this case, `run` can be called as `run`, `r` and `exec`: +//! You can create aliases if desired, which is as simple as adding extra names to the `cmd` definition. +//! In this case, `run` can be called as `run`, `r` and `exec`: //! //! ```rust //! xflags::xflags! { -//! cmd run alias r exec {} +//! cmd run r exec {} //! } //! ``` //!