Skip to content

Commit fe356f4

Browse files
authored
Align flag alias with normal use (#85)
* Align use of '-' and '--' with other CLI parsers The use a single '-' is usually for short options a single char. Also the way it is used in all tests. One of the example has an alias "ag" which currently is parsed if the argument is '-ag' with this change aliases with more the a single char need to be parsed with double dash - as e.g. '--ag' * Normalize args '-abcd=10' as '-a -b -c -d 10' This is also the normal behavior of CLI arg parsers - you can group mutilple short options with a single dash. * Refactor 'normalized_args' to common 'utils.rs' * Bump msrv rust 1.35.0 -> 1.67.0 * Add test * Handle both short and long aliases
1 parent 962bafa commit fe356f4

File tree

7 files changed

+101
-44
lines changed

7 files changed

+101
-44
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
allow_failure: false
3232
- build: msrv
3333
os: ubuntu-latest
34-
rust: 1.35.0
34+
rust: 1.67.0
3535
allow_failure: false
3636
steps:
3737
- uses: actions/checkout@master
@@ -48,4 +48,4 @@ jobs:
4848
- uses: actions/checkout@master
4949
- name: Install Rust
5050
run: rustup update stable && rustup default stable && rustup component add rustfmt
51-
- run: cargo fmt -- --check
51+
- run: cargo fmt -- --check

src/app.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::utils::normalized_args;
12
use crate::{
23
error::ActionError, error::ActionErrorKind, Action, ActionWithResult, Command, Context, Flag,
34
FlagType, Help,
@@ -164,7 +165,7 @@ impl App {
164165
/// let app = App::new("cli")
165166
/// .action(action);
166167
/// ```
167-
///
168+
///
168169
/// # Panics
169170
///
170171
/// You cannot set both action and action_with_result.
@@ -271,7 +272,7 @@ impl App {
271272
/// let result = app.run_with_result(args);
272273
/// ```
273274
pub fn run_with_result(&self, args: Vec<String>) -> Result<(), Box<dyn Error>> {
274-
let args = Self::normalized_args(args);
275+
let args = normalized_args(args);
275276
let (cmd_v, args_v) = match args.len() {
276277
1 => args.split_at(1),
277278
_ => args[1..].split_at(1),
@@ -336,22 +337,6 @@ impl App {
336337
}
337338
}
338339

339-
/// Split arg with "=" to unify arg notations.
340-
/// --flag=value => ["--flag", "value"]
341-
/// --flag value => ["--flag", "value"]
342-
fn normalized_args(raw_args: Vec<String>) -> Vec<String> {
343-
raw_args.iter().fold(Vec::<String>::new(), |mut acc, cur| {
344-
if cur.starts_with('-') && cur.contains('=') {
345-
let mut splitted_flag: Vec<String> =
346-
cur.splitn(2, '=').map(|s| s.to_owned()).collect();
347-
acc.append(&mut splitted_flag);
348-
} else {
349-
acc.push(cur.to_owned());
350-
}
351-
acc
352-
})
353-
}
354-
355340
fn flag_help_text(&self) -> String {
356341
let mut text = String::new();
357342
text += "Flags:\n";
@@ -366,11 +351,23 @@ impl App {
366351
let alias = match &f.alias {
367352
Some(alias) => alias
368353
.iter()
354+
.filter(|&a| a.len() == 1)
369355
.map(|a| format!("-{}", a))
370356
.collect::<Vec<String>>()
371357
.join(", "),
372358
None => String::new(),
373359
};
360+
361+
let long_alias = match &f.alias {
362+
Some(alias) => alias
363+
.iter()
364+
.filter(|a| a.len() > 1)
365+
.map(|a| format!("--{}", a))
366+
.collect::<Vec<String>>()
367+
.join(", "),
368+
None => String::new(),
369+
};
370+
374371
let val = match f.flag_type {
375372
FlagType::Int => int_val,
376373
FlagType::Float => float_val,
@@ -379,9 +376,17 @@ impl App {
379376
};
380377

381378
let help = if alias.is_empty() {
382-
format!("--{} {}", f.name, val)
379+
if long_alias.is_empty() {
380+
format!("--{} {}", f.name, val)
381+
} else {
382+
format!("{}, --{}, {}", long_alias, f.name, val)
383+
}
383384
} else {
384-
format!("{}, --{} {}", alias, f.name, val)
385+
if long_alias.is_empty() {
386+
format!("{}, --{} {}", alias, f.name, val)
387+
} else {
388+
format!("{}, {}, --{} {}", alias, long_alias, f.name, val)
389+
}
385390
};
386391

387392
(help, f.description.clone())

src/command.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::utils::normalized_args;
12
use crate::{Action, ActionWithResult, Context, Flag, FlagType, Help};
23
use std::error::Error;
34

@@ -242,23 +243,10 @@ impl Command {
242243
}
243244
}
244245

245-
fn normalized_args(raw_args: Vec<String>) -> Vec<String> {
246-
raw_args.iter().fold(Vec::<String>::new(), |mut acc, cur| {
247-
if cur.starts_with('-') && cur.contains('=') {
248-
let mut splitted_flag: Vec<String> =
249-
cur.splitn(2, '=').map(|s| s.to_owned()).collect();
250-
acc.append(&mut splitted_flag);
251-
} else {
252-
acc.push(cur.to_owned());
253-
}
254-
acc
255-
})
256-
}
257-
258246
/// Run command
259247
/// Call this function only from `App`
260248
pub fn run_with_result(&self, args: Vec<String>) -> Result<(), Box<dyn Error>> {
261-
let args = Self::normalized_args(args);
249+
let args = normalized_args(args);
262250

263251
match args.split_first() {
264252
Some((cmd, args_v)) => match self.select_command(cmd) {
@@ -349,11 +337,23 @@ impl Command {
349337
let alias = match &f.alias {
350338
Some(alias) => alias
351339
.iter()
340+
.filter(|a| a.len() == 1)
352341
.map(|a| format!("-{}", a))
353342
.collect::<Vec<String>>()
354343
.join(", "),
355344
None => String::new(),
356345
};
346+
347+
let long_alias = match &f.alias {
348+
Some(alias) => alias
349+
.iter()
350+
.filter(|a| a.len() > 1)
351+
.map(|a| format!("--{}", a))
352+
.collect::<Vec<String>>()
353+
.join(", "),
354+
None => String::new(),
355+
};
356+
357357
let val = match f.flag_type {
358358
FlagType::Int => int_val,
359359
FlagType::Float => float_val,
@@ -362,9 +362,17 @@ impl Command {
362362
};
363363

364364
let help = if alias.is_empty() {
365-
format!("--{} {}", f.name, val)
365+
if long_alias.is_empty() {
366+
format!("--{} {}", f.name, val)
367+
} else {
368+
format!("{}, --{}, {}", long_alias, f.name, val)
369+
}
366370
} else {
367-
format!("{}, --{} {}", alias, f.name, val)
371+
if long_alias.is_empty() {
372+
format!("{}, --{} {}", alias, f.name, val)
373+
} else {
374+
format!("{}, {}, --{} {}", alias, long_alias, f.name, val)
375+
}
368376
};
369377

370378
(help, f.description.clone())

src/context.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ impl Context {
362362
#[cfg(test)]
363363
mod tests {
364364
use crate::error::FlagError;
365+
use crate::utils::normalized_args;
365366
use crate::{Context, Flag, FlagType};
366367

367368
#[test]
@@ -377,10 +378,12 @@ mod tests {
377378
"100".to_string(),
378379
"--uint".to_string(),
379380
"1234567654321".to_string(),
380-
"--float".to_string(),
381+
"--float_alias".to_string(),
381382
"1.23".to_string(),
382383
"--float".to_string(),
383384
"1.44".to_string(),
385+
"-ga".to_string(),
386+
"atest".to_string(),
384387
"--invalid_float".to_string(),
385388
"invalid".to_string(),
386389
];
@@ -389,17 +392,23 @@ mod tests {
389392
Flag::new("string", FlagType::String),
390393
Flag::new("int", FlagType::Int),
391394
Flag::new("uint", FlagType::Uint),
392-
Flag::new("float", FlagType::Float).multiple(),
395+
Flag::new("float", FlagType::Float)
396+
.multiple()
397+
.alias("float_alias"),
398+
Flag::new("gbool", FlagType::Bool).alias("g"),
399+
Flag::new("alias", FlagType::String).alias("a"),
393400
Flag::new("invalid_float", FlagType::Float),
394401
Flag::new("not_specified", FlagType::String),
395402
];
396-
let context = Context::new(args, Some(flags), "".to_string());
403+
let context = Context::new(normalized_args(args), Some(flags), "".to_string());
397404

398405
assert_eq!(context.bool_flag("bool"), true);
399406
assert_eq!(context.string_flag("string"), Ok("test".to_string()));
400407
assert_eq!(context.int_flag("int"), Ok(100));
401408
assert_eq!(context.uint_flag("uint"), Ok(1234567654321));
402409
assert_eq!(context.float_flag("float"), Ok(1.23));
410+
assert_eq!(context.bool_flag("gbool"), true);
411+
assert_eq!(context.string_flag("alias"), Ok("atest".to_string()));
403412

404413
// string value arg, string flag, used as int
405414
assert_eq!(context.int_flag("string"), Err(FlagError::TypeError));

src/flag.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,14 @@ impl Flag {
135135
pub fn option_index(&self, v: &[String]) -> Option<usize> {
136136
match &self.alias {
137137
Some(alias) => v.iter().position(|r| {
138-
r == &format!("--{}", &self.name) || alias.iter().any(|a| r == &format!("-{}", a))
138+
r == &format!("--{}", &self.name)
139+
|| alias.iter().any(|a| {
140+
if a.len() > 1 {
141+
r == &format!("--{}", a)
142+
} else {
143+
r == &format!("-{}", a)
144+
}
145+
})
139146
}),
140147
None => v.iter().position(|r| r == &format!("--{}", &self.name)),
141148
}
@@ -184,15 +191,16 @@ mod tests {
184191
"cli".to_string(),
185192
"command".to_string(),
186193
"-a".to_string(),
194+
"--ag".to_string(),
187195
"--bool".to_string(),
188196
"-c".to_string(),
189197
];
190198
{
191199
let f = Flag::new("bool", FlagType::Bool);
192-
assert_eq!(f.option_index(&v), Some(3));
200+
assert_eq!(f.option_index(&v), Some(4));
193201
}
194202
{
195-
let f = Flag::new("age", FlagType::Bool).alias("a");
203+
let f = Flag::new("age", FlagType::Bool).alias("ag").alias("a");
196204
assert_eq!(f.option_index(&v), Some(2));
197205
}
198206
{

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod context;
55
pub mod error;
66
mod flag;
77
mod help;
8+
mod utils;
89

910
pub use action::{Action, ActionWithResult};
1011
pub use app::App;

src/utils.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Split arg with "=" to unify arg notations.
2+
/// --flag=value => ["--flag", "value"]
3+
/// --flag value => ["--flag", "value"]
4+
/// -abe => ["-a", "-b", "-e"]
5+
/// -abef=32 => ["-a", "-b", "-e", "-f", "32"]
6+
pub fn normalized_args(raw_args: Vec<String>) -> Vec<String> {
7+
raw_args.iter().fold(Vec::<String>::new(), |mut acc, cur| {
8+
if cur.starts_with('-') && !cur.starts_with("--") {
9+
if cur.contains('=') {
10+
let splitted_flag: Vec<String> = cur.splitn(2, '=').map(|s| s.to_owned()).collect();
11+
let short_named = splitted_flag[0].chars().skip(1).map(|c| format!("-{}", c));
12+
acc.append(&mut short_named.collect());
13+
acc.append(&mut splitted_flag[1..].to_vec());
14+
} else {
15+
let short_named = cur.chars().skip(1).map(|c| format!("-{}", c));
16+
acc.append(&mut short_named.collect());
17+
}
18+
} else if cur.starts_with('-') && cur.contains('=') {
19+
let mut splitted_flag: Vec<String> = cur.splitn(2, '=').map(|s| s.to_owned()).collect();
20+
acc.append(&mut splitted_flag);
21+
} else {
22+
acc.push(cur.to_owned());
23+
}
24+
acc
25+
})
26+
}

0 commit comments

Comments
 (0)