Skip to content

Commit 431e477

Browse files
committed
Updated dictionary and list sorting conditions
1 parent 8c5b3a8 commit 431e477

File tree

1 file changed

+68
-21
lines changed

1 file changed

+68
-21
lines changed

src/murfey/cli/generate_config.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from __future__ import annotations
2+
13
import argparse
24
import json
35
import re
46
from ast import literal_eval
57
from pathlib import Path
6-
from typing import Any, Optional, get_type_hints
8+
from typing import Any, Optional, Type, get_type_hints
79

810
import yaml
911
from pydantic import ValidationError
@@ -91,50 +93,76 @@ def confirm_duplicate(value: str):
9193

9294

9395
def 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

Comments
 (0)