Skip to content

Commit 873dcb5

Browse files
committed
WIP: Now supported named, value-less arguments like --verbose.
1 parent 22836f0 commit 873dcb5

File tree

2 files changed

+285
-13
lines changed

2 files changed

+285
-13
lines changed

examples/simple.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ const FOO_ITEM: Item<Output> = Item {
1010
parameters: &[
1111
Parameter::Mandatory("a"),
1212
Parameter::Optional("b"),
13-
Parameter::Named {
14-
parameter_name: "verbose",
15-
argument_name: "VALUE",
16-
},
13+
Parameter::Named("verbose"),
14+
// Parameter::NamedValue {
15+
// parameter_name: "level",
16+
// argument_name: "INT",
17+
// },
1718
],
1819
},
1920
command: "foo",
@@ -78,6 +79,7 @@ impl std::fmt::Write for Output {
7879

7980
fn main() {
8081
let window = initscr();
82+
window.scrollok(true);
8183
noecho();
8284
let mut buffer = [0u8; 64];
8385
let mut o = Output(window);
@@ -111,8 +113,38 @@ fn exit_root(_menu: &Menu<Output>, context: &mut Output) {
111113
writeln!(context, "In exit_root").unwrap();
112114
}
113115

114-
fn select_foo<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) {
116+
fn select_foo<'a>(menu: &Menu<Output>, item: &Item<Output>, args: &[&str], context: &mut Output) {
115117
writeln!(context, "In select_foo. Args = {:?}", args).unwrap();
118+
writeln!(
119+
context,
120+
"a = {:?}",
121+
::menu::argument_finder(item, args, "a")
122+
)
123+
.unwrap();
124+
writeln!(
125+
context,
126+
"b = {:?}",
127+
::menu::argument_finder(item, args, "b")
128+
)
129+
.unwrap();
130+
writeln!(
131+
context,
132+
"verbose = {:?}",
133+
::menu::argument_finder(item, args, "verbose")
134+
)
135+
.unwrap();
136+
writeln!(
137+
context,
138+
"level = {:?}",
139+
::menu::argument_finder(item, args, "level")
140+
)
141+
.unwrap();
142+
writeln!(
143+
context,
144+
"no_such_arg = {:?}",
145+
::menu::argument_finder(item, args, "no_such_arg")
146+
)
147+
.unwrap();
116148
}
117149

118150
fn select_bar<'a>(_menu: &Menu<Output>, _item: &Item<Output>, args: &[&str], context: &mut Output) {

src/lib.rs

Lines changed: 248 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ type ItemCallbackFn<T> = fn(menu: &Menu<T>, item: &Item<T>, args: &[&str], conte
66
#[derive(Debug)]
77
/// Describes a parameter to the command
88
pub enum Parameter<'a> {
9+
/// A mandatory positional parameter
910
Mandatory(&'a str),
11+
/// An optional positional parameter. Must come after the mandatory positional arguments.
1012
Optional(&'a str),
11-
Named {
13+
/// A named parameter with no argument (e.g. `--verbose` or `--dry-run`)
14+
Named(&'a str),
15+
/// A named parameter with argument (e.g. `--mode=foo` or `--level=3`)
16+
NamedValue {
1217
parameter_name: &'a str,
1318
argument_name: &'a str,
1419
},
@@ -62,6 +67,104 @@ where
6267
pub context: &'a mut T,
6368
}
6469

70+
/// Looks for the named parameter in the parameter list of the item, then
71+
/// finds the correct argument.
72+
///
73+
/// * Returns `Ok(None)` if `parameter_name` gives an optional or named
74+
/// parameter and that argument was not given.
75+
/// * Returns `Ok(arg)` if the argument corresponding to `parameter_name` was
76+
/// found. `arg` is the empty string if the parameter was `Parameter::Named`
77+
/// (and hence doesn't take a value).
78+
/// * Returns `Err(())` if `parameter_name` was not in `item.parameter_list`
79+
/// or `item` wasn't an Item::Callback
80+
pub fn argument_finder<'a, T>(
81+
item: &'a Item<'a, T>,
82+
argument_list: &'a [&'a str],
83+
name_to_find: &'a str,
84+
) -> Result<Option<&'a str>, ()> {
85+
if let ItemType::Callback { parameters, .. } = item.item_type {
86+
// Step 1 - Find `name_to_find` in the parameter list.
87+
let mut found_param = None;
88+
let mut mandatory_count = 0;
89+
let mut optional_count = 0;
90+
for param in parameters.iter() {
91+
match param {
92+
Parameter::Mandatory(name) => {
93+
mandatory_count += 1;
94+
if *name == name_to_find {
95+
found_param = Some((param, mandatory_count));
96+
}
97+
}
98+
Parameter::Optional(name) => {
99+
optional_count += 1;
100+
if *name == name_to_find {
101+
found_param = Some((param, optional_count));
102+
}
103+
}
104+
Parameter::Named(name) => {
105+
if *name == name_to_find {
106+
found_param = Some((param, 0));
107+
}
108+
}
109+
_ => {
110+
unimplemented!();
111+
}
112+
}
113+
}
114+
// Step 2 - What sort of parameter is it?
115+
match found_param {
116+
// Step 2a - Mandatory Positional
117+
Some((Parameter::Mandatory(_name), mandatory_idx)) => {
118+
// We want positional parameter number `mandatory_idx` of `mandatory_count`.
119+
let mut positional_args_seen = 0;
120+
for arg in argument_list {
121+
if !arg.starts_with("--") {
122+
// Positional
123+
positional_args_seen += 1;
124+
if positional_args_seen == mandatory_idx {
125+
return Ok(Some(arg));
126+
}
127+
}
128+
}
129+
// Valid thing to ask for but we don't have it
130+
Ok(None)
131+
}
132+
// Step 2b - Optional Positional
133+
Some((Parameter::Optional(_name), optional_idx)) => {
134+
// We want positional parameter number `mandatory_idx` of `mandatory_count`.
135+
let mut positional_args_seen = 0;
136+
for arg in argument_list {
137+
if !arg.starts_with("--") {
138+
// Positional
139+
positional_args_seen += 1;
140+
if positional_args_seen == (mandatory_count + optional_idx) {
141+
return Ok(Some(arg));
142+
}
143+
}
144+
}
145+
// Valid thing to ask for but we don't have it
146+
Ok(None)
147+
}
148+
// Step 2c - Named
149+
Some((Parameter::Named(name), _)) => {
150+
for arg in argument_list {
151+
if arg.starts_with("--") && (&arg[2..] == *name) {
152+
return Ok(Some(""));
153+
}
154+
}
155+
// Valid thing to ask for but we don't have it
156+
Ok(None)
157+
}
158+
// Step 2d - NamedValue
159+
// Step 2e - not found
160+
_ => Err(()),
161+
}
162+
} else {
163+
// Not an item with arguments
164+
Err(())
165+
}
166+
}
167+
65168
enum Outcome {
66169
CommandProcessed,
67170
NeedMore,
@@ -239,7 +342,10 @@ where
239342
Parameter::Optional(name) => {
240343
write!(self.context, " [ <{}> ]", name).unwrap();
241344
}
242-
Parameter::Named {
345+
Parameter::Named(name) => {
346+
write!(self.context, " [ --{} ]", name).unwrap();
347+
}
348+
Parameter::NamedValue {
243349
parameter_name,
244350
argument_name,
245351
} => {
@@ -280,6 +386,14 @@ where
280386
_ => false,
281387
})
282388
.count();
389+
let positional_parameter_count = parameters
390+
.iter()
391+
.filter(|p| match p {
392+
Parameter::Mandatory(_) => true,
393+
Parameter::Optional(_) => true,
394+
_ => false,
395+
})
396+
.count();
283397
if command.len() >= item.command.len() {
284398
// Maybe arguments
285399
let mut argument_buffer: [&str; 16] = [""; 16];
@@ -291,19 +405,49 @@ where
291405
{
292406
*slot = arg;
293407
argument_count += 1;
294-
if !arg.starts_with("--") {
408+
if arg.starts_with("--") {
409+
// Validate named argument
410+
let mut found = false;
411+
for param in parameters.iter() {
412+
match param {
413+
Parameter::Named(name) => {
414+
if &arg[2..] == *name {
415+
found = true;
416+
break;
417+
}
418+
}
419+
Parameter::NamedValue { parameter_name, .. } => {
420+
if let Some(name) = arg[2..].split('=').next() {
421+
if name == *parameter_name {
422+
found = true;
423+
break;
424+
}
425+
}
426+
}
427+
_ => {
428+
// Ignore
429+
}
430+
}
431+
}
432+
if !found {
433+
writeln!(context, "Error: Did not understand {:?}", arg).unwrap();
434+
return;
435+
}
436+
} else {
295437
positional_arguments += 1;
296438
}
297439
}
298-
if positional_arguments >= mandatory_parameter_count {
440+
if positional_arguments < mandatory_parameter_count {
441+
writeln!(context, "Error: Insufficient arguments given").unwrap();
442+
} else if positional_arguments > positional_parameter_count {
443+
writeln!(context, "Error: Too many arguments given").unwrap();
444+
} else {
299445
callback_function(
300446
parent_menu,
301447
item,
302448
&argument_buffer[0..argument_count],
303449
context,
304450
);
305-
} else {
306-
writeln!(context, "Error: Insufficient arguments given").unwrap();
307451
}
308452
} else {
309453
// Definitely no arguments
@@ -318,8 +462,104 @@ where
318462

319463
#[cfg(test)]
320464
mod tests {
465+
use super::*;
466+
467+
fn dummy(_menu: &Menu<u32>, _item: &Item<u32>, _args: &[&str], _context: &mut u32) {}
468+
469+
#[test]
470+
fn find_arg_mandatory() {
471+
let item = Item {
472+
command: "dummy",
473+
help: None,
474+
item_type: ItemType::Callback {
475+
function: dummy,
476+
parameters: &[
477+
Parameter::Mandatory("foo"),
478+
Parameter::Mandatory("bar"),
479+
Parameter::Mandatory("baz"),
480+
],
481+
},
482+
};
483+
assert_eq!(
484+
argument_finder(&item, &["a", "b", "c"], "foo"),
485+
Ok(Some("a"))
486+
);
487+
assert_eq!(
488+
argument_finder(&item, &["a", "b", "c"], "bar"),
489+
Ok(Some("b"))
490+
);
491+
assert_eq!(
492+
argument_finder(&item, &["a", "b", "c"], "baz"),
493+
Ok(Some("c"))
494+
);
495+
// Not an argument
496+
assert_eq!(argument_finder(&item, &["a", "b", "c"], "quux"), Err(()));
497+
}
498+
321499
#[test]
322-
fn it_works() {
323-
assert_eq!(2 + 2, 4);
500+
fn find_arg_optional() {
501+
let item = Item {
502+
command: "dummy",
503+
help: None,
504+
item_type: ItemType::Callback {
505+
function: dummy,
506+
parameters: &[
507+
Parameter::Mandatory("foo"),
508+
Parameter::Mandatory("bar"),
509+
Parameter::Optional("baz"),
510+
],
511+
},
512+
};
513+
assert_eq!(
514+
argument_finder(&item, &["a", "b", "c"], "foo"),
515+
Ok(Some("a"))
516+
);
517+
assert_eq!(
518+
argument_finder(&item, &["a", "b", "c"], "bar"),
519+
Ok(Some("b"))
520+
);
521+
assert_eq!(
522+
argument_finder(&item, &["a", "b", "c"], "baz"),
523+
Ok(Some("c"))
524+
);
525+
// Not an argument
526+
assert_eq!(argument_finder(&item, &["a", "b", "c"], "quux"), Err(()));
527+
// Missing optional
528+
assert_eq!(argument_finder(&item, &["a", "b"], "baz"), Ok(None));
529+
}
530+
531+
#[test]
532+
fn find_arg_named() {
533+
let item = Item {
534+
command: "dummy",
535+
help: None,
536+
item_type: ItemType::Callback {
537+
function: dummy,
538+
parameters: &[
539+
Parameter::Mandatory("foo"),
540+
Parameter::Named("bar"),
541+
Parameter::Named("baz"),
542+
],
543+
},
544+
};
545+
assert_eq!(
546+
argument_finder(&item, &["a", "--bar", "--baz"], "foo"),
547+
Ok(Some("a"))
548+
);
549+
assert_eq!(
550+
argument_finder(&item, &["a", "--bar", "--baz"], "bar"),
551+
Ok(Some(""))
552+
);
553+
assert_eq!(
554+
argument_finder(&item, &["a", "--bar", "--baz"], "baz"),
555+
Ok(Some(""))
556+
);
557+
// Not an argument
558+
assert_eq!(
559+
argument_finder(&item, &["a", "--bar", "--baz"], "quux"),
560+
Err(())
561+
);
562+
// Missing named
563+
assert_eq!(argument_finder(&item, &["a"], "baz"), Ok(None));
324564
}
325565
}

0 commit comments

Comments
 (0)