Skip to content

Commit

Permalink
Merge pull request #39 from O-X-E-Y/master
Browse files Browse the repository at this point in the history
add an `alias` keyword to the parser
  • Loading branch information
matklad committed Dec 18, 2023
2 parents 5b33711 + 5ab8146 commit f6e05b2
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 2 deletions.
1 change: 1 addition & 0 deletions crates/xflags-macros/src/ast.rs
Expand Up @@ -13,6 +13,7 @@ impl XFlags {
#[derive(Debug)]
pub(crate) struct Cmd {
pub(crate) name: String,
pub(crate) aliases: Vec<String>,
pub(crate) doc: Option<String>,
pub(crate) args: Vec<Arg>,
pub(crate) flags: Vec<Flag>,
Expand Down
10 changes: 8 additions & 2 deletions crates/xflags-macros/src/emit.rs
Expand Up @@ -242,7 +242,9 @@ 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::<Vec<_>>().join(" | ");
w!(buf, "({}, {}) => state_ = {},\n", cmd.idx, sub_match, sub.idx);
}
if !cmd.args.is_empty() || cmd.has_subcommands() {
w!(buf, "({}, _) => {{\n", cmd.idx);
Expand Down Expand Up @@ -406,7 +408,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::<Vec<_>>().join(" | ");
w!(buf, "{}{}\n", prefix, idens);
}
if let Some(doc) = &cmd.doc {
empty_help = false;
Expand Down Expand Up @@ -483,6 +486,9 @@ impl ast::Cmd {
}
camel(&self.name)
}
pub(crate) fn all_identifiers(&self) -> impl Iterator<Item = &String> {
[&self.name].into_iter().chain(self.aliases.iter())
}
fn cmd_enum_ident(&self) -> String {
format!("{}Cmd", self.ident())
}
Expand Down
22 changes: 22 additions & 0 deletions crates/xflags-macros/src/parse.rs
Expand Up @@ -82,11 +82,14 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result<ast::Cmd> {
cmd_name(p)?
};

let aliases = alias_names(p);

let idx = p.idx;
p.idx += 1;

let mut res = ast::Cmd {
name,
aliases,
doc: None,
args: Vec::new(),
flags: Vec::new(),
Expand Down Expand Up @@ -135,6 +138,15 @@ fn cmd_impl(p: &mut Parser, anon: bool) -> Result<ast::Cmd> {
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)
}

Expand Down Expand Up @@ -242,6 +254,16 @@ fn cmd_name(p: &mut Parser) -> Result<String> {
Ok(name)
}

fn alias_names(p: &mut Parser) -> Vec<String> {
let mut aliases = vec![];

while let Some(alias) = p.eat_name() {
aliases.push(alias);
}

aliases
}

fn flag_name(p: &mut Parser) -> Result<String> {
let name = p.expect_name()?;
if !name.starts_with('-') {
Expand Down
11 changes: 11 additions & 0 deletions 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 {}
}
}
109 changes: 109 additions & 0 deletions 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<usize>,
}

#[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> {
Self::from_env_()
}

#[allow(dead_code)]
pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
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<Self> {
let mut p = xflags::rt::Parser::new_from_env();
Self::parse_(&mut p)
}
fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {
let mut p = xflags::rt::Parser::new(args);
Self::parse_(&mut p)
}
}

impl AliasCmd {
fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> {
#![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::<usize>(&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 <count>
Little sanity check to see if this still works as intended
alias-cmd this | one | has | a | lot | of | aliases
";
}
9 changes: 9 additions & 0 deletions crates/xflags/src/lib.rs
Expand Up @@ -162,6 +162,15 @@
//! }
//! ```
//!
//! 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 r exec {}
//! }
//! ```
//!
//! Nesting **cmd** is allowed. `xflag` automatically generates boilerplate
//! enums for subcommands:
//!
Expand Down

0 comments on commit f6e05b2

Please sign in to comment.