@@ -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 ) ]
885889pub struct ParsedArgs {
886890 values : Vec < ParsedArgument > ,
891+ index_by_name : HashMap < String , usize > ,
887892 subcommand : Option < ( String , Box < ParsedArgs > ) > ,
888893}
889894
890895impl 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