Skip to content

Commit 7ed95d1

Browse files
authored
new: added new methods for supporting sub command (#35)
1 parent 9492709 commit 7ed95d1

File tree

8 files changed

+846
-13
lines changed

8 files changed

+846
-13
lines changed

src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ pub struct Cmd<'a> {
318318
cfgs: Vec<OptCfg>,
319319

320320
_leaked_strs: Vec<&'a str>,
321+
_num_of_args: usize,
321322
}
322323

323324
impl<'a> Drop for Cmd<'a> {
@@ -339,7 +340,7 @@ impl fmt::Debug for Cmd<'_> {
339340
}
340341
}
341342

342-
impl<'a> Cmd<'a> {
343+
impl<'b, 'a> Cmd<'a> {
343344
/// Creates a `Cmd` instance with command line arguments obtained from [std::env::args_os].
344345
///
345346
/// Since [std::env::args_os] returns a vector of [OsString] and they can contain invalid
@@ -413,12 +414,15 @@ impl<'a> Cmd<'a> {
413414
cmd_name_start = 0;
414415
}
415416

417+
let _num_of_args = _leaked_strs.len();
418+
416419
Ok(Cmd {
417420
name: &_leaked_strs[0][cmd_name_start..],
418421
args: Vec::new(),
419422
opts: HashMap::new(),
420423
cfgs: Vec::new(),
421424
_leaked_strs,
425+
_num_of_args,
422426
})
423427
}
424428

@@ -449,15 +453,26 @@ impl<'a> Cmd<'a> {
449453
cmd_name_start = 0;
450454
};
451455

456+
let _num_of_args = _leaked_strs.len();
457+
452458
Cmd {
453459
name: &_leaked_strs[0][cmd_name_start..],
454460
args: Vec::new(),
455461
opts: HashMap::new(),
456462
cfgs: Vec::new(),
457463
_leaked_strs,
464+
_num_of_args,
458465
}
459466
}
460467

468+
fn sub_cmd(&'a self, from_index: usize) -> Cmd<'b> {
469+
Cmd::with_strings(
470+
self._leaked_strs[from_index..(self._num_of_args)]
471+
.into_iter()
472+
.map(|s| s.to_string()),
473+
)
474+
}
475+
461476
/// Returns the command name.
462477
///
463478
/// This name is base name extracted from the command path string slice, which is the first

src/parse/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ where
2828

2929
'L0: for (i_arg, arg) in args.iter().enumerate() {
3030
if is_non_opt {
31-
collect_args(arg);
3231
if until_1st_arg {
3332
if let Some(err) = first_err {
3433
return Err(err);
3534
}
3635
return Ok(Some(i_arg));
3736
}
37+
collect_args(arg);
3838
} else if !prev_opt_taking_args.is_empty() {
3939
match collect_opts(prev_opt_taking_args, Some(arg)) {
4040
Err(err) => {
@@ -107,13 +107,13 @@ where
107107
}
108108
} else if arg.starts_with("-") {
109109
if arg.len() == 1 {
110-
collect_args(arg);
111110
if until_1st_arg {
112111
if let Some(err) = first_err {
113112
return Err(err);
114113
}
115114
return Ok(Some(i_arg));
116115
}
116+
collect_args(arg);
117117
continue 'L0;
118118
}
119119

@@ -176,13 +176,13 @@ where
176176
}
177177
}
178178
} else {
179-
collect_args(arg);
180179
if until_1st_arg {
181180
if let Some(err) = first_err {
182181
return Err(err);
183182
}
184183
return Ok(Some(i_arg));
185184
}
185+
collect_args(arg);
186186
}
187187
}
188188

src/parse/parse.rs

Lines changed: 231 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::parse_args;
66
use crate::errors::InvalidOption;
77
use crate::Cmd;
88

9-
impl<'a> Cmd<'a> {
9+
impl<'b, 'a> Cmd<'a> {
1010
/// Parses command line arguments without configurations.
1111
///
1212
/// This method divides command line arguments into options and command arguments based on
@@ -28,6 +28,7 @@ impl<'a> Cmd<'a> {
2828
/// use cliargs::errors::InvalidOption;
2929
///
3030
/// let mut cmd = Cmd::with_strings(vec![ /* ... */ ]);
31+
///
3132
/// match cmd.parse() {
3233
/// Ok(_) => { /* ... */ },
3334
/// Err(InvalidOption::OptionContainsInvalidChar { option }) => {
@@ -51,9 +52,9 @@ impl<'a> Cmd<'a> {
5152

5253
let take_opt_args = |_arg: &str| false;
5354

54-
if !self._leaked_strs.is_empty() {
55+
if self._num_of_args > 0 {
5556
match parse_args(
56-
&self._leaked_strs[1..],
57+
&self._leaked_strs[1..(self._num_of_args)],
5758
collect_args,
5859
collect_opts,
5960
take_opt_args,
@@ -66,6 +67,64 @@ impl<'a> Cmd<'a> {
6667

6768
Ok(())
6869
}
70+
71+
/// Parses command line arguments without configurations but stops parsing when encountering
72+
/// first command argument.
73+
///
74+
/// This method creates and returns a new [Cmd] instance that holds the command line arguments
75+
/// starting from the first command argument.
76+
///
77+
/// This method parses command line arguments in the same way as the [Cmd::parse] method, except
78+
/// that it only parses the command line arguments before the first command argument.
79+
///
80+
/// ```rust
81+
/// use cliargs::Cmd;
82+
/// use cliargs::errors::InvalidOption;
83+
///
84+
/// let mut cmd = Cmd::with_strings(vec![ /* ... */ ]);
85+
///
86+
/// match cmd.parse_until_sub_cmd() {
87+
/// Ok(Some(mut sub_cmd)) => {
88+
/// let sub_cmd_name = sub_cmd.name();
89+
/// match sub_cmd.parse() {
90+
/// Ok(_) => { /* ... */ },
91+
/// Err(err) => panic!("Invalid option: {}", err.option()),
92+
/// }
93+
/// },
94+
/// Ok(None) => { /* ... */ },
95+
/// Err(InvalidOption::OptionContainsInvalidChar { option }) => {
96+
/// panic!("Option contains invalid character: {option}");
97+
/// },
98+
/// Err(err) => panic!("Invalid option: {}", err.option()),
99+
/// }
100+
/// ```
101+
pub fn parse_until_sub_cmd(&mut self) -> Result<Option<Cmd<'b>>, InvalidOption> {
102+
let collect_args = |_arg| {};
103+
104+
let collect_opts = |name, option| {
105+
let vec = self.opts.entry(name).or_insert_with(|| Vec::new());
106+
if let Some(arg) = option {
107+
vec.push(arg);
108+
}
109+
Ok(())
110+
};
111+
112+
let take_opt_args = |_arg: &str| false;
113+
114+
if self._num_of_args > 0 {
115+
if let Some(idx) = parse_args(
116+
&self._leaked_strs[1..(self._num_of_args)],
117+
collect_args,
118+
collect_opts,
119+
take_opt_args,
120+
true,
121+
)? {
122+
return Ok(Some(self.sub_cmd(idx + 1))); // +1, because parse_args parses from 1.
123+
}
124+
}
125+
126+
Ok(None)
127+
}
69128
}
70129

71130
#[cfg(test)]
@@ -572,3 +631,172 @@ mod tests_of_cmd {
572631
}
573632
}
574633
}
634+
635+
#[cfg(test)]
636+
mod tests_of_parse_until_sub_cmd {
637+
use super::*;
638+
639+
#[test]
640+
fn test_if_command_line_arguments_contains_no_command_argument_and_option() {
641+
let ui_args = vec!["/path/to/app".to_string()];
642+
let mut cmd = Cmd::with_strings(ui_args);
643+
644+
match cmd.parse_until_sub_cmd() {
645+
Ok(None) => {}
646+
Ok(Some(_)) => assert!(false),
647+
Err(_) => assert!(false),
648+
}
649+
650+
assert_eq!(cmd.name(), "app");
651+
assert_eq!(cmd.args(), &[] as &[&str]);
652+
}
653+
654+
#[test]
655+
fn test_if_command_line_arguments_contains_only_command_arguments() {
656+
let ui_args = vec![
657+
"/path/to/app".to_string(),
658+
"foo".to_string(),
659+
"bar".to_string(),
660+
];
661+
let mut cmd = Cmd::with_strings(ui_args);
662+
663+
match cmd.parse_until_sub_cmd() {
664+
Ok(Some(mut sub_cmd)) => {
665+
assert_eq!(sub_cmd.name(), "foo");
666+
assert_eq!(sub_cmd.args(), &[] as &[&str]);
667+
668+
match sub_cmd.parse() {
669+
Ok(_) => {}
670+
Err(_) => assert!(false),
671+
}
672+
673+
assert_eq!(sub_cmd.name(), "foo");
674+
assert_eq!(sub_cmd.args(), &["bar"]);
675+
}
676+
Ok(None) => assert!(false),
677+
Err(_) => assert!(false),
678+
}
679+
680+
assert_eq!(cmd.name(), "app");
681+
assert_eq!(cmd.args(), &[] as &[&str]);
682+
683+
//
684+
685+
let f = || {
686+
let ui_args = vec![
687+
"/path/to/app".to_string(),
688+
"foo".to_string(),
689+
"bar".to_string(),
690+
];
691+
let mut cmd = Cmd::with_strings(ui_args);
692+
693+
if let Some(mut sub_cmd) = cmd.parse_until_sub_cmd()? {
694+
assert_eq!(sub_cmd.name(), "foo");
695+
assert_eq!(sub_cmd.args(), &[] as &[&str]);
696+
697+
match sub_cmd.parse() {
698+
Ok(_) => {}
699+
Err(_) => assert!(false),
700+
}
701+
702+
assert_eq!(sub_cmd.name(), "foo");
703+
assert_eq!(sub_cmd.args(), &["bar"]);
704+
705+
assert_eq!(cmd.name(), "app");
706+
assert_eq!(cmd.args(), &[] as &[&str]);
707+
} else {
708+
assert_eq!(cmd.name(), "app");
709+
assert_eq!(cmd.args(), &[] as &[&str]);
710+
}
711+
712+
Ok::<(), InvalidOption>(())
713+
};
714+
let _ = f();
715+
}
716+
717+
#[test]
718+
fn test_if_command_line_arguments_contains_only_command_options() {
719+
let ui_args = vec![
720+
"/path/to/app".to_string(),
721+
"--foo".to_string(),
722+
"-b".to_string(),
723+
];
724+
let mut cmd = Cmd::with_strings(ui_args);
725+
726+
match cmd.parse_until_sub_cmd() {
727+
Ok(None) => {}
728+
Ok(Some(_)) => assert!(false),
729+
Err(_) => assert!(false),
730+
}
731+
732+
assert_eq!(cmd.name(), "app");
733+
assert_eq!(cmd.args(), &[] as &[&str]);
734+
assert_eq!(cmd.has_opt("foo"), true);
735+
assert_eq!(cmd.has_opt("b"), true);
736+
assert_eq!(cmd.opt_arg("foo"), None);
737+
assert_eq!(cmd.opt_arg("b"), None);
738+
}
739+
740+
#[test]
741+
fn test_if_command_line_arguments_contains_both_command_arguments_and_options() {
742+
let ui_args = vec![
743+
"/path/to/app".to_string(),
744+
"--foo=123".to_string(),
745+
"bar".to_string(),
746+
"--baz".to_string(),
747+
"-q=ABC".to_string(),
748+
"quux".to_string(),
749+
];
750+
let mut cmd = Cmd::with_strings(ui_args);
751+
752+
if let Some(mut sub_cmd) = cmd.parse_until_sub_cmd().unwrap() {
753+
assert_eq!(sub_cmd.name(), "bar");
754+
assert_eq!(sub_cmd.args(), &[] as &[&str]);
755+
assert_eq!(cmd.has_opt("baz"), false);
756+
assert_eq!(cmd.opt_arg("baz"), None);
757+
assert_eq!(cmd.has_opt("q"), false);
758+
assert_eq!(cmd.opt_arg("q"), None);
759+
760+
match sub_cmd.parse() {
761+
Ok(_) => {}
762+
Err(_) => assert!(false),
763+
}
764+
765+
assert_eq!(sub_cmd.name(), "bar");
766+
assert_eq!(sub_cmd.args(), &["quux"]);
767+
assert_eq!(sub_cmd.has_opt("baz"), true);
768+
assert_eq!(sub_cmd.opt_arg("baz"), None);
769+
assert_eq!(sub_cmd.has_opt("q"), true);
770+
assert_eq!(sub_cmd.opt_arg("q"), Some("ABC"));
771+
}
772+
773+
assert_eq!(cmd.name(), "app");
774+
assert_eq!(cmd.args(), &[] as &[&str]);
775+
assert_eq!(cmd.has_opt("foo"), true);
776+
assert_eq!(cmd.opt_arg("foo"), Some("123"));
777+
}
778+
779+
#[test]
780+
fn test_if_fail_to_parse() {
781+
let ui_args = vec![
782+
"/path/to/app".to_string(),
783+
"--f#o".to_string(),
784+
"bar".to_string(),
785+
];
786+
let mut cmd = Cmd::with_strings(ui_args);
787+
788+
match cmd.parse_until_sub_cmd() {
789+
Ok(None) => assert!(false),
790+
Ok(Some(_)) => assert!(false),
791+
Err(InvalidOption::OptionContainsInvalidChar { option }) => {
792+
assert_eq!(option, "f#o");
793+
}
794+
Err(_) => assert!(false),
795+
}
796+
797+
assert_eq!(cmd.name(), "app");
798+
assert_eq!(cmd.args(), &[] as &[&str]);
799+
assert_eq!(cmd.has_opt("f#o"), false);
800+
assert_eq!(cmd.opt_arg("f#o"), None);
801+
}
802+
}

0 commit comments

Comments
 (0)