@@ -257,137 +257,7 @@ def get_args(
257257 Normally if `allow_spaces` is false, it will take a space as the end of an args value.
258258 If it is true, it will take spaces as part of the value up until the next arg-flag is found.
259259 (Multiple spaces will become one space in the value.)"""
260- results_positional : dict [str , ArgResultPositional ] = {}
261- results_regular : dict [str , ArgResultRegular ] = {}
262- positional_configs : dict [str , str ] = {}
263- arg_lookup : dict [str , str ] = {}
264- before_count , after_count = 0 , 0
265- args_len = len (args := _sys .argv [1 :])
266-
267- # PARSE 'find_args' CONFIGURATION
268- for alias , config in find_args .items ():
269- flags : Optional [set [str ]] = None
270- default_value : Optional [str ] = None
271-
272- if isinstance (config , str ):
273- # HANDLE POSITIONAL ARGUMENT COLLECTION
274- if config == "before" :
275- before_count += 1
276- if before_count > 1 :
277- raise ValueError ("Only one alias can have the value 'before' for positional argument collection." )
278- elif config == "after" :
279- after_count += 1
280- if after_count > 1 :
281- raise ValueError ("Only one alias can have the value 'after' for positional argument collection." )
282- else :
283- raise ValueError (
284- f"Invalid positional argument type '{ config } ' for alias '{ alias } '.\n "
285- "Must be either 'before' or 'after'."
286- )
287- positional_configs [alias ] = config
288- results_positional [alias ] = {"exists" : False , "values" : []}
289- elif isinstance (config , set ):
290- flags = config
291- results_regular [alias ] = {"exists" : False , "value" : default_value }
292- elif isinstance (config , dict ):
293- flags , default_value = config .get ("flags" ), config .get ("default" )
294- results_regular [alias ] = {"exists" : False , "value" : default_value }
295- else :
296- raise TypeError (
297- f"Invalid configuration type for alias '{ alias } '.\n "
298- "Must be a set, dict, literal 'before' or literal 'after'."
299- )
300-
301- # BUILD FLAG LOOKUP FOR NON-POSITIONAL ARGUMENTS
302- if flags is not None :
303- for flag in flags :
304- if flag in arg_lookup :
305- raise ValueError (
306- f"Duplicate flag '{ flag } ' found. It's assigned to both '{ arg_lookup [flag ]} ' and '{ alias } '."
307- )
308- arg_lookup [flag ] = alias
309-
310- # FIND POSITIONS OF FIRST AND LAST FLAGS FOR POSITIONAL ARGUMENT COLLECTION
311- first_flag_pos : Optional [int ] = None
312- last_flag_with_value_pos : Optional [int ] = None
313-
314- for i , arg in enumerate (args ):
315- if arg in arg_lookup :
316- if first_flag_pos is None :
317- first_flag_pos = i
318- # CHECK IF THIS FLAG HAS A VALUE FOLLOWING IT
319- if i + 1 < args_len and args [i + 1 ] not in arg_lookup :
320- if not allow_spaces :
321- last_flag_with_value_pos = i + 1
322- else :
323- # FIND THE END OF THE MULTI-WORD VALUE
324- j = i + 1
325- while j < args_len and args [j ] not in arg_lookup :
326- j += 1
327- last_flag_with_value_pos = j - 1
328-
329- # COLLECT "before" POSITIONAL ARGUMENTS
330- for alias , pos_type in positional_configs .items ():
331- if pos_type == "before" :
332- before_args : list [str ] = []
333- end_pos : int = first_flag_pos if first_flag_pos is not None else args_len
334- for i in range (end_pos ):
335- if args [i ] not in arg_lookup :
336- before_args .append (args [i ])
337- if before_args :
338- results_positional [alias ]["values" ] = before_args
339- results_positional [alias ]["exists" ] = len (before_args ) > 0
340-
341- # PROCESS FLAGGED ARGUMENTS
342- i = 0
343- while i < args_len :
344- arg = args [i ]
345- if (opt_alias := arg_lookup .get (arg )) is not None :
346- results_regular [opt_alias ]["exists" ] = True
347- value_found_after_flag : bool = False
348- if i + 1 < args_len and args [i + 1 ] not in arg_lookup :
349- if not allow_spaces :
350- results_regular [opt_alias ]["value" ] = args [i + 1 ]
351- i += 1
352- value_found_after_flag = True
353- else :
354- value_parts = []
355- j = i + 1
356- while j < args_len and args [j ] not in arg_lookup :
357- value_parts .append (args [j ])
358- j += 1
359- if value_parts :
360- results_regular [opt_alias ]["value" ] = " " .join (value_parts )
361- i = j - 1
362- value_found_after_flag = True
363- if not value_found_after_flag :
364- results_regular [opt_alias ]["value" ] = None
365- i += 1
366-
367- # COLLECT "after" POSITIONAL ARGUMENTS
368- for alias , pos_type in positional_configs .items ():
369- if pos_type == "after" :
370- after_args : list [str ] = []
371- start_pos : int = (last_flag_with_value_pos + 1 ) if last_flag_with_value_pos is not None else 0
372- # IF NO FLAGS WERE FOUND WITH VALUES, START AFTER THE LAST FLAG
373- if last_flag_with_value_pos is None and first_flag_pos is not None :
374- # FIND THE LAST FLAG POSITION
375- last_flag_pos = None
376- for i , arg in enumerate (args ):
377- if arg in arg_lookup :
378- last_flag_pos = i
379- if last_flag_pos is not None :
380- start_pos = last_flag_pos + 1
381-
382- for i in range (start_pos , args_len ):
383- if args [i ] not in arg_lookup :
384- after_args .append (args [i ])
385-
386- if after_args :
387- results_positional [alias ]["values" ] = after_args
388- results_positional [alias ]["exists" ] = len (after_args ) > 0
389-
390- return Args (** results_positional , ** results_regular )
260+ return _ConsoleArgsParseHelper (allow_spaces = allow_spaces , find_args = find_args )()
391261
392262 @classmethod
393263 def pause_exit (
@@ -1099,6 +969,174 @@ def _multiline_input_submit(event: KeyPressEvent) -> None:
1099969 event .app .exit (result = event .app .current_buffer .document .text )
1100970
1101971
972+ class _ConsoleArgsParseHelper :
973+ """Internal, callable helper class to parse command-line arguments."""
974+
975+ def __init__ (
976+ self ,
977+ allow_spaces : bool ,
978+ find_args : dict [str , set [str ] | ArgConfigWithDefault | Literal ["before" , "after" ]],
979+ ):
980+ self .allow_spaces = allow_spaces
981+ self .find_args = find_args
982+
983+ self .results_positional : dict [str , ArgResultPositional ] = {}
984+ self .results_regular : dict [str , ArgResultRegular ] = {}
985+ self .positional_configs : dict [str , str ] = {}
986+ self .arg_lookup : dict [str , str ] = {}
987+
988+ self .args = _sys .argv [1 :]
989+ self .args_len = len (self .args )
990+ self .first_flag_pos : Optional [int ] = None
991+ self .last_flag_with_value_pos : Optional [int ] = None
992+
993+ def __call__ (self ) -> Args :
994+ self .parse_configuration ()
995+ self .find_flag_positions ()
996+ self .process_positional_args ()
997+ self .process_flagged_args ()
998+ return Args (** self .results_positional , ** self .results_regular )
999+
1000+ def parse_configuration (self ) -> None :
1001+ """Parse the `find_args` configuration and build lookup structures."""
1002+ before_count , after_count = 0 , 0
1003+
1004+ for alias , config in self .find_args .items ():
1005+ flags : Optional [set [str ]] = None
1006+ default_value : Optional [str ] = None
1007+
1008+ if isinstance (config , str ):
1009+ # HANDLE POSITIONAL ARGUMENT COLLECTION
1010+ if config == "before" :
1011+ before_count += 1
1012+ if before_count > 1 :
1013+ raise ValueError ("Only one alias can have the value 'before' for positional argument collection." )
1014+ elif config == "after" :
1015+ after_count += 1
1016+ if after_count > 1 :
1017+ raise ValueError ("Only one alias can have the value 'after' for positional argument collection." )
1018+ else :
1019+ raise ValueError (
1020+ f"Invalid positional argument type '{ config } ' for alias '{ alias } '.\n "
1021+ "Must be either 'before' or 'after'."
1022+ )
1023+ self .positional_configs [alias ] = config
1024+ self .results_positional [alias ] = {"exists" : False , "values" : []}
1025+ elif isinstance (config , set ):
1026+ flags = config
1027+ self .results_regular [alias ] = {"exists" : False , "value" : default_value }
1028+ elif isinstance (config , dict ):
1029+ flags , default_value = config .get ("flags" ), config .get ("default" )
1030+ self .results_regular [alias ] = {"exists" : False , "value" : default_value }
1031+ else :
1032+ raise TypeError (
1033+ f"Invalid configuration type for alias '{ alias } '.\n "
1034+ "Must be a set, dict, literal 'before' or literal 'after'."
1035+ )
1036+
1037+ # BUILD FLAG LOOKUP FOR NON-POSITIONAL ARGUMENTS
1038+ if flags is not None :
1039+ for flag in flags :
1040+ if flag in self .arg_lookup :
1041+ raise ValueError (
1042+ f"Duplicate flag '{ flag } ' found. It's assigned to both '{ self .arg_lookup [flag ]} ' and '{ alias } '."
1043+ )
1044+ self .arg_lookup [flag ] = alias
1045+
1046+ def find_flag_positions (self ) -> None :
1047+ """Find positions of first and last flags for positional argument collection."""
1048+ for i , arg in enumerate (self .args ):
1049+ if arg in self .arg_lookup :
1050+ if self .first_flag_pos is None :
1051+ self .first_flag_pos = i
1052+
1053+ # CHECK IF THIS FLAG HAS A VALUE FOLLOWING IT
1054+ if i + 1 < self .args_len and self .args [i + 1 ] not in self .arg_lookup :
1055+ if not self .allow_spaces :
1056+ self .last_flag_with_value_pos = i + 1
1057+
1058+ else :
1059+ # FIND THE END OF THE MULTI-WORD VALUE
1060+ j = i + 1
1061+ while j < self .args_len and self .args [j ] not in self .arg_lookup :
1062+ j += 1
1063+
1064+ self .last_flag_with_value_pos = j - 1
1065+
1066+ def process_positional_args (self ) -> None :
1067+ """Collect positional `"before"/"after"` arguments."""
1068+ for alias , pos_type in self .positional_configs .items ():
1069+ if pos_type == "before" :
1070+ before_args : list [str ] = []
1071+ end_pos : int = self .first_flag_pos if self .first_flag_pos is not None else self .args_len
1072+
1073+ for i in range (end_pos ):
1074+ if self .args [i ] not in self .arg_lookup :
1075+ before_args .append (self .args [i ])
1076+
1077+ if before_args :
1078+ self .results_positional [alias ]["values" ] = before_args
1079+ self .results_positional [alias ]["exists" ] = len (before_args ) > 0
1080+
1081+ if pos_type == "after" :
1082+ after_args : list [str ] = []
1083+ start_pos : int = (self .last_flag_with_value_pos + 1 ) if self .last_flag_with_value_pos is not None else 0
1084+
1085+ # IF NO FLAGS WERE FOUND WITH VALUES, START AFTER THE LAST FLAG
1086+ if self .last_flag_with_value_pos is None and self .first_flag_pos is not None :
1087+ # FIND THE LAST FLAG POSITION
1088+ last_flag_pos : Optional [int ] = None
1089+ for i , arg in enumerate (self .args ):
1090+ if arg in self .arg_lookup :
1091+ last_flag_pos = i
1092+
1093+ if last_flag_pos is not None :
1094+ start_pos = last_flag_pos + 1
1095+
1096+ for i in range (start_pos , self .args_len ):
1097+ if self .args [i ] not in self .arg_lookup :
1098+ after_args .append (self .args [i ])
1099+
1100+ if after_args :
1101+ self .results_positional [alias ]["values" ] = after_args
1102+ self .results_positional [alias ]["exists" ] = len (after_args ) > 0
1103+
1104+ def process_flagged_args (self ) -> None :
1105+ """Process normal flagged arguments."""
1106+ i = 0
1107+
1108+ while i < self .args_len :
1109+ arg = self .args [i ]
1110+
1111+ if (opt_alias := self .arg_lookup .get (arg )) is not None :
1112+ self .results_regular [opt_alias ]["exists" ] = True
1113+ value_found_after_flag : bool = False
1114+
1115+ if i + 1 < self .args_len and self .args [i + 1 ] not in self .arg_lookup :
1116+ if not self .allow_spaces :
1117+ self .results_regular [opt_alias ]["value" ] = self .args [i + 1 ]
1118+ i += 1
1119+ value_found_after_flag = True
1120+
1121+ else :
1122+ value_parts = []
1123+
1124+ j = i + 1
1125+ while j < self .args_len and self .args [j ] not in self .arg_lookup :
1126+ value_parts .append (self .args [j ])
1127+ j += 1
1128+
1129+ if value_parts :
1130+ self .results_regular [opt_alias ]["value" ] = " " .join (value_parts )
1131+ i = j - 1
1132+ value_found_after_flag = True
1133+
1134+ if not value_found_after_flag :
1135+ self .results_regular [opt_alias ]["value" ] = None
1136+
1137+ i += 1
1138+
1139+
11021140class _ConsoleLogBoxBgReplacer :
11031141 """Internal, callable class to replace matched text with background-colored text for log boxes."""
11041142
0 commit comments