1+ from __future__ import annotations
2+
13import argparse
24import json
35import re
46from ast import literal_eval
57from pathlib import Path
6- from typing import Any , Optional , get_type_hints
8+ from typing import Any , Optional , Type , get_type_hints
79
810import yaml
911from pydantic import ValidationError
@@ -91,50 +93,76 @@ def confirm_duplicate(value: str):
9193
9294
9395def construct_list (
94- list_name : str ,
96+ value_name : str ,
9597 prompt_message : str ,
9698 allow_empty : bool = False ,
9799 allow_eval : bool = True ,
98100 many_types : bool = True ,
101+ restrict_to_types : Optional [Type [Any ] | tuple [Type [Any ]]] = None ,
102+ sort_values : bool = True ,
99103 debug : bool = False ,
100104) -> list [Any ]:
101105 """
102106 Helper function to facilitate interactive construction of a list to be stored
103107 under the current parameter.
104108 """
105109 lst : list = []
106- add_entry = ask_for_input (list_name , False )
110+ add_entry = ask_for_input (value_name , False )
107111 message = prompt_message
108112 while add_entry is True :
109113 value = prompt (message , style = "yellow" ).strip ()
110114 # Reject empty inputs if set
111115 if not value and not allow_empty :
112116 console .print ("No value provided." , style = "red" )
113- add_entry = ask_for_input (list_name , True )
117+ add_entry = ask_for_input (value_name , True )
114118 continue
115- # Convert numericals if set
119+ # Convert values if set
116120 try :
117- eval_value = (
118- literal_eval (value )
119- if allow_eval and isinstance (literal_eval (value ), (int , float , complex ))
120- else value
121- )
121+ eval_value = literal_eval (value )
122122 except Exception :
123123 eval_value = value
124+ # Check if it's a permitted type (continue to allow None as value)
125+ if restrict_to_types is not None :
126+ allowed_types = (
127+ (restrict_to_types ,)
128+ if not isinstance (restrict_to_types , (list , tuple ))
129+ else restrict_to_types
130+ )
131+ if not isinstance (eval_value , allowed_types ):
132+ console .print (
133+ f"The provided value ({ type (eval_value )} ) is not an allowed type." ,
134+ style = "red" ,
135+ )
136+ add_entry = ask_for_input (value_name , True )
137+ continue
124138 # Confirm if duplicate entry should be added
125139 if eval_value in lst and confirm_duplicate (str (eval_value )) is False :
126- add_entry = ask_for_input (list_name , True )
140+ add_entry = ask_for_input (value_name , True )
127141 continue
128142 lst .append (eval_value )
129143 # Reject list with multiple types if set
130144 if not many_types and len ({type (item ) for item in lst }) > 1 :
131145 console .print (
132- "The provided value is of a different type to the other members. \n "
133- "It won't be added to the list." ,
146+ "The provided value is of a different type to the other members. It "
147+ "won't be added to the list." ,
134148 style = "red" ,
135149 )
136150 lst = lst [:- 1 ]
137- add_entry = ask_for_input (list_name , True )
151+ # Sort values if set
152+ # Sort numeric values differently from alphanumeric ones
153+ lst = (
154+ sorted (
155+ lst ,
156+ key = lambda v : (
157+ (0 , float (v ))
158+ if isinstance (v , (int , float ))
159+ else (1 , abs (v ), v .real ) if isinstance (v , complex ) else (2 , str (v ))
160+ ),
161+ )
162+ if sort_values
163+ else lst
164+ )
165+ add_entry = ask_for_input (value_name , True )
138166 continue
139167 return lst
140168
@@ -152,6 +180,21 @@ def construct_dict(
152180 """
153181 Helper function to facilitate interative construction of a dictionary.
154182 """
183+
184+ def is_type (value : str , instance : Type [Any ] | tuple [Type [Any ], ...]) -> bool :
185+ """
186+ Checks if the string provided evaluates to one of the desired types
187+ """
188+ instance = (instance ,) if not isinstance (instance , (list , tuple )) else instance
189+ try :
190+ eval_value = literal_eval (value )
191+ except Exception :
192+ eval_value = value
193+ return isinstance (eval_value , instance )
194+
195+ """
196+ Start of construct_dict
197+ """
155198 dct : dict = {}
156199 add_entry = ask_for_input (dict_name , False )
157200 key_message = f"Please enter the { key_name } "
@@ -176,25 +219,29 @@ def construct_dict(
176219 continue
177220 # Convert values to numericals if set
178221 try :
179- eval_value = (
180- literal_eval (value )
181- if allow_eval and isinstance (literal_eval (value ), (int , float , complex ))
182- else value
183- )
222+ eval_value = literal_eval (value )
184223 except Exception :
185224 eval_value = value
186225 dct [key ] = eval_value
187226 add_entry = ask_for_input (dict_name , True )
188227 continue
189228
190229 # Sort keys if set
230+ # Sort numeric keys separately from alphanumeric ones
191231 dct = (
192232 {
193233 key : dct [key ]
194234 for key in sorted (
195235 dct .keys (),
196- # Sort numeric keys as numerals and alphanumeric keys alphabetically
197- key = (lambda k : (0 , float (k ) if str (k ).isdigit () else (1 , str (k )))),
236+ key = lambda k : (
237+ (0 , float (k ))
238+ if is_type (k , (int , float ))
239+ else (
240+ (1 , abs (complex (k )), complex (k ).real )
241+ if is_type (k , complex )
242+ else (2 , str (k ))
243+ )
244+ ),
198245 )
199246 }
200247 if sort_keys
0 commit comments