Skip to content

Commit 090dbe8

Browse files
committed
[update] the argument lookup to utilize a hashmap and change positional lookup to keep track of the current index
1 parent ab6f5d9 commit 090dbe8

1 file changed

Lines changed: 86 additions & 66 deletions

File tree

  • crates/lambda-rs-args/src

crates/lambda-rs-args/src/lib.rs

Lines changed: 86 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ impl ArgumentParser {
432432
// Errors are returned, not panicked.
433433
let mut collecting_values = false;
434434
let mut last_key: Option<String> = None;
435+
let mut next_positional_idx: usize = 0;
435436

436437
let mut parsed_arguments = vec![];
437438
parsed_arguments.resize(
@@ -459,6 +460,7 @@ impl ArgumentParser {
459460
let sub_parsed = sub.parse(&argv)?;
460461
return Ok(ParsedArgs {
461462
values: vec![],
463+
index_by_name: HashMap::new(),
462464
subcommand: Some((t.to_string(), Box::new(sub_parsed))),
463465
});
464466
}
@@ -477,6 +479,7 @@ impl ArgumentParser {
477479
let sub_parsed = sub.parse(&argv2)?;
478480
return Ok(ParsedArgs {
479481
values: vec![],
482+
index_by_name: HashMap::new(),
480483
subcommand: Some((arg_token.to_string(), Box::new(sub_parsed))),
481484
});
482485
}
@@ -487,7 +490,11 @@ impl ArgumentParser {
487490

488491
if arg_token == "--" {
489492
for value in iter.by_ref() {
490-
self.assign_next_positional(&mut parsed_arguments, value.as_str())?;
493+
self.assign_next_positional(
494+
&mut parsed_arguments,
495+
value.as_str(),
496+
&mut next_positional_idx,
497+
)?;
491498
}
492499
break;
493500
}
@@ -753,10 +760,7 @@ impl ArgumentParser {
753760
}
754761
}
755762

756-
Ok(ParsedArgs {
757-
values: parsed_arguments,
758-
subcommand: None,
759-
})
763+
Ok(ParsedArgs::new(parsed_arguments, None))
760764
}
761765

762766
/// Backwards‑compatible panicking API. Prefer `parse` for non‑panicking use.
@@ -884,92 +888,92 @@ impl std::error::Error for ArgsError {}
884888
#[derive(Debug, Clone)]
885889
pub struct ParsedArgs {
886890
values: Vec<ParsedArgument>,
891+
index_by_name: HashMap<String, usize>,
887892
subcommand: Option<(String, Box<ParsedArgs>)>,
888893
}
889894

890895
impl ParsedArgs {
896+
fn new(
897+
values: Vec<ParsedArgument>,
898+
subcommand: Option<(String, Box<ParsedArgs>)>,
899+
) -> Self {
900+
let mut index_by_name: HashMap<String, usize> =
901+
HashMap::with_capacity(values.len());
902+
for (idx, arg) in values.iter().enumerate() {
903+
// Keys should be unique; keep the first occurrence if duplicates exist.
904+
index_by_name.entry(arg.name.clone()).or_insert(idx);
905+
}
906+
Self {
907+
values,
908+
index_by_name,
909+
subcommand,
910+
}
911+
}
912+
891913
/// Convert into the raw underlying `(name, value)` vector.
892914
pub fn into_vec(self) -> Vec<ParsedArgument> {
893915
self.values
894916
}
895917

896918
/// True if the named argument is present (and not `None`).
897919
pub fn has(&self, name: &str) -> bool {
898-
self
899-
.values
900-
.iter()
901-
.any(|p| p.name == name && !matches!(p.value, ArgumentValue::None))
920+
let Some(idx) = self.index_by_name.get(name) else {
921+
return false;
922+
};
923+
return !matches!(self.values[*idx].value, ArgumentValue::None);
902924
}
903925

904926
/// Get a `String` value by name, if present and typed as string.
905927
pub fn get_string(&self, name: &str) -> Option<String> {
906-
self
907-
.values
908-
.iter()
909-
.find(|p| p.name == name)
910-
.and_then(|p| match &p.value {
911-
ArgumentValue::String(s) => Some(s.clone()),
912-
_ => None,
913-
})
928+
let idx = self.index_by_name.get(name)?;
929+
match &self.values[*idx].value {
930+
ArgumentValue::String(s) => Some(s.clone()),
931+
_ => None,
932+
}
914933
}
915934

916935
/// Get an `i64` value by name, if present and typed as integer.
917936
pub fn get_i64(&self, name: &str) -> Option<i64> {
918-
self
919-
.values
920-
.iter()
921-
.find(|p| p.name == name)
922-
.and_then(|p| match &p.value {
923-
ArgumentValue::Integer(v) => Some(*v),
924-
_ => None,
925-
})
937+
let idx = self.index_by_name.get(name)?;
938+
match &self.values[*idx].value {
939+
ArgumentValue::Integer(v) => Some(*v),
940+
_ => None,
941+
}
926942
}
927943

928944
/// Get an `f32` value by name, if present and typed as float.
929945
pub fn get_f32(&self, name: &str) -> Option<f32> {
930-
self
931-
.values
932-
.iter()
933-
.find(|p| p.name == name)
934-
.and_then(|p| match &p.value {
935-
ArgumentValue::Float(v) => Some(*v),
936-
_ => None,
937-
})
946+
let idx = self.index_by_name.get(name)?;
947+
match &self.values[*idx].value {
948+
ArgumentValue::Float(v) => Some(*v),
949+
_ => None,
950+
}
938951
}
939952

940953
/// Get an `f64` value by name, if present and typed as double.
941954
pub fn get_f64(&self, name: &str) -> Option<f64> {
942-
self
943-
.values
944-
.iter()
945-
.find(|p| p.name == name)
946-
.and_then(|p| match &p.value {
947-
ArgumentValue::Double(v) => Some(*v),
948-
_ => None,
949-
})
955+
let idx = self.index_by_name.get(name)?;
956+
match &self.values[*idx].value {
957+
ArgumentValue::Double(v) => Some(*v),
958+
_ => None,
959+
}
950960
}
951961

952962
/// Get a `bool` value by name, if present and typed as boolean.
953963
pub fn get_bool(&self, name: &str) -> Option<bool> {
954-
self
955-
.values
956-
.iter()
957-
.find(|p| p.name == name)
958-
.and_then(|p| match &p.value {
959-
ArgumentValue::Boolean(v) => Some(*v),
960-
_ => None,
961-
})
964+
let idx = self.index_by_name.get(name)?;
965+
match &self.values[*idx].value {
966+
ArgumentValue::Boolean(v) => Some(*v),
967+
_ => None,
968+
}
962969
}
963970

964971
pub fn get_count(&self, name: &str) -> Option<i64> {
965-
self
966-
.values
967-
.iter()
968-
.find(|p| p.name == name)
969-
.and_then(|p| match &p.value {
970-
ArgumentValue::Integer(v) => Some(*v),
971-
_ => None,
972-
})
972+
let idx = self.index_by_name.get(name)?;
973+
match &self.values[*idx].value {
974+
ArgumentValue::Integer(v) => Some(*v),
975+
_ => None,
976+
}
973977
}
974978

975979
pub fn subcommand(&self) -> Option<(&str, &ParsedArgs)> {
@@ -1069,6 +1073,17 @@ mod tests {
10691073
assert_eq!(p.get_string("rest").unwrap(), "b");
10701074
}
10711075

1076+
#[test]
1077+
fn has_respects_none_and_unknown() {
1078+
let parser = ArgumentParser::new("app")
1079+
.with_argument(Argument::new("--opt").with_type(ArgumentType::String))
1080+
.with_argument(Argument::new("--flag").with_type(ArgumentType::Boolean));
1081+
let p = parser.parse(&argv(&["--flag"])).unwrap();
1082+
assert!(p.has("--flag"));
1083+
assert!(!p.has("--opt"));
1084+
assert!(!p.has("--does-not-exist"));
1085+
}
1086+
10721087
#[test]
10731088
fn counting_and_cluster() {
10741089
let parser = ArgumentParser::new("app").with_argument(
@@ -1283,17 +1298,22 @@ impl ArgumentParser {
12831298
&mut self,
12841299
out: &mut [ParsedArgument],
12851300
value: &str,
1301+
next_positional_idx: &mut usize,
12861302
) -> Result<(), ArgsError> {
1287-
for pname in self.positionals.clone() {
1288-
if let Some(entry) = self.args.get_mut(&pname) {
1289-
if !entry.1 {
1290-
let parsed = parse_value(&entry.0, value)?;
1291-
let idx = entry.2;
1292-
out[idx] = ParsedArgument::new(entry.0.name.as_str(), parsed);
1293-
entry.1 = true;
1294-
return Ok(());
1295-
}
1303+
while *next_positional_idx < self.positionals.len() {
1304+
let pname = &self.positionals[*next_positional_idx];
1305+
*next_positional_idx += 1;
1306+
let Some(entry) = self.args.get_mut(pname) else {
1307+
continue;
1308+
};
1309+
if entry.1 {
1310+
continue;
12961311
}
1312+
let parsed = parse_value(&entry.0, value)?;
1313+
let idx = entry.2;
1314+
out[idx] = ParsedArgument::new(entry.0.name.as_str(), parsed);
1315+
entry.1 = true;
1316+
return Ok(());
12971317
}
12981318
Err(ArgsError::InvalidValue {
12991319
name: "<positional>".to_string(),

0 commit comments

Comments
 (0)