untils 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- untils/__init__.py +28 -0
- untils/command.py +130 -0
- untils/command_system.py +403 -0
- untils/commands_config.py +23 -0
- untils/config_validator.py +616 -0
- untils/factories.py +40 -0
- untils/input_token.py +104 -0
- untils/input_validator.py +556 -0
- untils/ioreader.py +55 -0
- untils/iovalidator.py +55 -0
- untils/parser.py +138 -0
- untils/processor.py +88 -0
- untils/settings.py +114 -0
- untils/tokenizer.py +108 -0
- untils/utils/__init__.py +22 -0
- untils/utils/constants.py +267 -0
- untils/utils/decorators.py +47 -0
- untils/utils/enums.py +48 -0
- untils/utils/lib_warnings.py +43 -0
- untils/utils/protocols.py +42 -0
- untils/utils/type_aliases.py +88 -0
- untils-1.0.0.dist-info/METADATA +73 -0
- untils-1.0.0.dist-info/RECORD +26 -0
- untils-1.0.0.dist-info/WHEEL +5 -0
- untils-1.0.0.dist-info/licenses/README.md +50 -0
- untils-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
"""config_validator.py - Deep config validation."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnnecessaryIsInstance=false
|
|
4
|
+
# ^^^^^^^ (Raw dynamic data checking.)
|
|
5
|
+
|
|
6
|
+
from typing import Dict, get_args, Optional, List, Any
|
|
7
|
+
|
|
8
|
+
from string import punctuation
|
|
9
|
+
|
|
10
|
+
from untils.utils.type_aliases import (
|
|
11
|
+
ConfigType, UnknownConfigType, ConfigVersion, UnknownCommandClass, CommandClass, CommandType,
|
|
12
|
+
UnknownCommandConfig, CommandStates, InternalCommandStates
|
|
13
|
+
)
|
|
14
|
+
from untils.utils.enums import ConfigVersions, WarningsLevel
|
|
15
|
+
from untils.utils.lib_warnings import (
|
|
16
|
+
ConfigStructureWarning, ConfigValuesWarning, ConfigStructureError, ConfigValuesError
|
|
17
|
+
)
|
|
18
|
+
from untils.utils.constants import Constants, Strings
|
|
19
|
+
|
|
20
|
+
from untils.settings import Settings
|
|
21
|
+
|
|
22
|
+
class ConfigValidator:
|
|
23
|
+
"""Validator class for config structure and semantic."""
|
|
24
|
+
|
|
25
|
+
_empty_name_replace_index: int = -1
|
|
26
|
+
"""Private variable, which uses for invalid names renaming. It prevents name duplication."""
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def validate_version(settings: Settings, config_dict: UnknownConfigType) -> ConfigVersion:
|
|
30
|
+
"""Validates the config version.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
settings: The settings.
|
|
34
|
+
config_dict: The config dictionary, which was not validated yet.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
`ConfigVersion` if config version is valid, else returns the latest version in `Constants.LATEST_CONFIG_VERSION`.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ConfigStructureWarning: `version` field is not found in config.
|
|
41
|
+
ConfigValuesWarning: Config version is not supported or invalid.
|
|
42
|
+
|
|
43
|
+
ConfigStructureError: `version` field is not found in config.
|
|
44
|
+
ConfigValuesError: Config version is not supported or invalid.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
version: int = -1
|
|
48
|
+
|
|
49
|
+
if "version" in config_dict:
|
|
50
|
+
version = config_dict["version"]
|
|
51
|
+
|
|
52
|
+
if version not in ConfigVersions:
|
|
53
|
+
settings.warning(
|
|
54
|
+
Strings.INVALID_CONFIG_VERSION.substitute(version=repr(version)),
|
|
55
|
+
Strings.AUTO_CORRECT_TO_LATEST,
|
|
56
|
+
ConfigValuesWarning,
|
|
57
|
+
ConfigValuesError
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
settings.warning(
|
|
61
|
+
Strings.INVALID_CONFIG_VERSION.substitute(version=Strings.UNKNOWN_VERSION),
|
|
62
|
+
Strings.AUTO_CORRECT_TO_LATEST,
|
|
63
|
+
ConfigStructureWarning,
|
|
64
|
+
ConfigStructureError
|
|
65
|
+
)
|
|
66
|
+
version = Constants.LATEST_CONFIG_VERSION.value
|
|
67
|
+
|
|
68
|
+
return version
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def validate_command_type(
|
|
72
|
+
settings: Settings,
|
|
73
|
+
command_dict: UnknownCommandClass
|
|
74
|
+
) -> Optional[CommandType]:
|
|
75
|
+
"""Validates a command type.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
settings: The settings.
|
|
79
|
+
command_dict: The command dictionary, which was not validated yet.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
`CommandType` if the command type was validated successfully, else `None`.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ConfigValuesWarning: The command type field is not written.
|
|
86
|
+
|
|
87
|
+
ConfigValuesErorr: The command type is not valid or unknown.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
command_type: Optional[CommandType] = None
|
|
91
|
+
|
|
92
|
+
if "type" in command_dict:
|
|
93
|
+
if command_dict["type"] in get_args(CommandType):
|
|
94
|
+
command_type = command_dict["type"]
|
|
95
|
+
else:
|
|
96
|
+
settings.warning(
|
|
97
|
+
Strings.COMMAND_INVALID_TYPE,
|
|
98
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
99
|
+
ConfigValuesWarning,
|
|
100
|
+
ConfigValuesError
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
settings.warning(
|
|
104
|
+
Strings.COMMAND_UNKNOWN_TYPE,
|
|
105
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
106
|
+
ConfigStructureWarning,
|
|
107
|
+
ConfigStructureError
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return command_type
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def validate_command_aliases(
|
|
114
|
+
settings: Settings,
|
|
115
|
+
command_dict: UnknownCommandClass
|
|
116
|
+
) -> List[str]:
|
|
117
|
+
"""Validates command aliases from a command dictionary.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
settings: The settings.
|
|
121
|
+
command_dict: The command dictionary, which was not validated yet.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Returns validated aliases.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ConfigStructureWarning: The command aliases are not written or is not list.
|
|
128
|
+
ConfigValuesWarning: An alias in the command aliases is not string or duplicates previous.
|
|
129
|
+
|
|
130
|
+
ConfigStructureError: The command aliases are not written or is not list.
|
|
131
|
+
ConfigValuesError: An alias in the command aliases is not string or duplicates previous.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
aliases: List[str] = []
|
|
135
|
+
|
|
136
|
+
if "aliases" in command_dict:
|
|
137
|
+
if not isinstance(command_dict["aliases"], list):
|
|
138
|
+
settings.warning(
|
|
139
|
+
Strings.COMMAND_INVALID_ALIASES,
|
|
140
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
141
|
+
ConfigStructureWarning,
|
|
142
|
+
ConfigStructureError
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
for alias in command_dict["aliases"]:
|
|
146
|
+
if not isinstance(alias, str):
|
|
147
|
+
settings.warning(
|
|
148
|
+
Strings.COMMAND_ALIAS_INVALID.substitute(alias=alias),
|
|
149
|
+
Strings.AUTO_CORRECT_WITH_CASTING,
|
|
150
|
+
ConfigValuesWarning,
|
|
151
|
+
ConfigValuesError
|
|
152
|
+
)
|
|
153
|
+
alias = str(alias)
|
|
154
|
+
|
|
155
|
+
if alias in aliases:
|
|
156
|
+
settings.warning(
|
|
157
|
+
Strings.COMMAND_ALIAS_COPIED.substitute(alias=alias),
|
|
158
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
159
|
+
ConfigValuesWarning,
|
|
160
|
+
ConfigValuesError
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
aliases.append(alias)
|
|
164
|
+
|
|
165
|
+
return aliases
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def validate_command_default(command_dict: UnknownCommandClass) -> Any:
|
|
169
|
+
"""Validates a command default value.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
command_dict: The command dictionary, which was not validated yet.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
`Any` if the default value is exists, else `None`.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
if "default" in command_dict:
|
|
179
|
+
return command_dict["default"]
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def validate_command(
|
|
184
|
+
settings: Settings,
|
|
185
|
+
command_dict: UnknownCommandClass
|
|
186
|
+
) -> Optional[CommandClass]:
|
|
187
|
+
"""Validates a command dictionary.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
settings: The settings.
|
|
191
|
+
command_dict: The command dictionary, which was not validated yet.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
`CommandClass` if command was validated successfully, else `None`. Also `None` returns if a type field is not written or required fields for this type are not written.
|
|
195
|
+
|
|
196
|
+
Raises:
|
|
197
|
+
ConfigStructureWarning: Structure of the command is not valid.
|
|
198
|
+
ConfigValuesWarning: The command values is not valid.
|
|
199
|
+
|
|
200
|
+
ConfigStructureError: Structure of the command is not valid.
|
|
201
|
+
ConfigValuesError: The command values is not valid.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
command_type: Optional[CommandType] = ConfigValidator.validate_command_type(
|
|
205
|
+
settings,
|
|
206
|
+
command_dict
|
|
207
|
+
)
|
|
208
|
+
if command_type is None:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
arguments: UnknownCommandConfig = {
|
|
212
|
+
"type": command_type
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if command_type in ("word", "flag", "option"):
|
|
216
|
+
arguments["aliases"] = ConfigValidator.validate_command_aliases(settings, command_dict)
|
|
217
|
+
|
|
218
|
+
if command_type not in ("word", "flag", "option") and "aliases" in command_dict:
|
|
219
|
+
settings.warning(
|
|
220
|
+
Strings.COMMAND_INVALID_TYPE,
|
|
221
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
222
|
+
ConfigStructureWarning,
|
|
223
|
+
ConfigStructureError
|
|
224
|
+
)
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
if command_type in ("fallback", "flag", "option"):
|
|
228
|
+
arguments["default"] = ConfigValidator.validate_command_default(command_dict)
|
|
229
|
+
if command_type not in ("fallback", "flag", "option") and "default" in command_dict:
|
|
230
|
+
settings.warning(
|
|
231
|
+
Strings.COMMAND_INVALID_TYPE,
|
|
232
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
233
|
+
ConfigStructureWarning,
|
|
234
|
+
ConfigStructureError
|
|
235
|
+
)
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
if command_type in ("word", "fallback"):
|
|
239
|
+
arguments["children"] = {}
|
|
240
|
+
|
|
241
|
+
if "children" in command_dict:
|
|
242
|
+
for k, cd in command_dict["children"].items():
|
|
243
|
+
k = ConfigValidator.validate_name(
|
|
244
|
+
settings,
|
|
245
|
+
k,
|
|
246
|
+
is_fallback=(cd.get("type") == "fallback")
|
|
247
|
+
)
|
|
248
|
+
child: Optional[CommandClass] = ConfigValidator.validate_command(settings, cd)
|
|
249
|
+
|
|
250
|
+
if child is not None:
|
|
251
|
+
arguments["children"][k] = child
|
|
252
|
+
|
|
253
|
+
if arguments["children"] == {}:
|
|
254
|
+
settings.warning(
|
|
255
|
+
Strings.COMMAND_INVALID_CHILDREN,
|
|
256
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
257
|
+
ConfigValuesWarning,
|
|
258
|
+
ConfigValuesError
|
|
259
|
+
)
|
|
260
|
+
if command_type not in ("word", "fallback") and "children" in command_dict:
|
|
261
|
+
settings.warning(
|
|
262
|
+
Strings.COMMAND_INVALID_TYPE,
|
|
263
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
264
|
+
ConfigStructureWarning,
|
|
265
|
+
ConfigStructureError
|
|
266
|
+
)
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
return arguments # pyright: ignore[reportReturnType] (The arguments are always correct.)
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def validate_name(
|
|
273
|
+
settings: Settings,
|
|
274
|
+
name: str,
|
|
275
|
+
is_state: bool=False,
|
|
276
|
+
is_fallback: bool=False
|
|
277
|
+
) -> str:
|
|
278
|
+
"""Validates an identifier name.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
settings: The settings.
|
|
282
|
+
name: The string name.
|
|
283
|
+
is_state: Is this validation for state.
|
|
284
|
+
is_fallback: Is this validation for the `Fallback` command type.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Validated and corrected string name.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ConfigValuesWarning: The name is empty, has '-' character in name start, has special characters, has invalid `InternalState` structure (if `is_state == True`), has invalid `Fallback` structure (if `is_fallback == True`) or character is not valid.
|
|
291
|
+
|
|
292
|
+
ConfigValuesError: The name is empty, has '-' character in name start, has special characters, has invalid `InternalState` structure (if `is_state == True`), has invalid `Fallback` structure (if `is_fallback == True`) or character is not valid.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
if not isinstance(name, str):
|
|
296
|
+
settings.warning(
|
|
297
|
+
Strings.COMMAND_INVALID_ALIASES,
|
|
298
|
+
Strings.AUTO_CORRECT_WITH_CASTING,
|
|
299
|
+
ConfigValuesWarning,
|
|
300
|
+
ConfigValuesError
|
|
301
|
+
)
|
|
302
|
+
name = str(name)
|
|
303
|
+
|
|
304
|
+
if name == ''.join([" "] * len(name)):
|
|
305
|
+
settings.warning(
|
|
306
|
+
Strings.COMMAND_NAME_EMPTY,
|
|
307
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
308
|
+
ConfigValuesWarning,
|
|
309
|
+
ConfigValuesError
|
|
310
|
+
)
|
|
311
|
+
ConfigValidator._empty_name_replace_index += 1
|
|
312
|
+
return f"command+{ConfigValidator._empty_name_replace_index}"
|
|
313
|
+
|
|
314
|
+
i: int = 0
|
|
315
|
+
found_letter: bool = False
|
|
316
|
+
found_dollar: bool = False
|
|
317
|
+
removing_indexes: List[int] = []
|
|
318
|
+
specials = set(punctuation)
|
|
319
|
+
specials.add(' ')
|
|
320
|
+
specials.remove('-')
|
|
321
|
+
while i < len(name):
|
|
322
|
+
if name[i] == '-' and not found_letter:
|
|
323
|
+
# '-' as invalid separator.
|
|
324
|
+
settings.warning(
|
|
325
|
+
Strings.COMMAND_NAME_STARTS_INVALID,
|
|
326
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
327
|
+
ConfigValuesWarning,
|
|
328
|
+
ConfigValuesError
|
|
329
|
+
)
|
|
330
|
+
removing_indexes.append(i)
|
|
331
|
+
elif name[i] in specials:
|
|
332
|
+
# Character is special.
|
|
333
|
+
if is_state:
|
|
334
|
+
if name[i] == "_":
|
|
335
|
+
# Internal state name validation.
|
|
336
|
+
start: int = i
|
|
337
|
+
while i < len(name) and name[i] == "_":
|
|
338
|
+
i += 1
|
|
339
|
+
if i - start == 2:
|
|
340
|
+
continue
|
|
341
|
+
settings.warning(
|
|
342
|
+
Strings.STATE_INTERNAL_NAME_INVALID.substitute(length=i - start),
|
|
343
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
344
|
+
ConfigValuesWarning,
|
|
345
|
+
ConfigValuesError
|
|
346
|
+
)
|
|
347
|
+
name = name[:start] + "__" + name[i:]
|
|
348
|
+
i = start + 2
|
|
349
|
+
elif name[i] in ("-", ":", "/"):
|
|
350
|
+
# Allowed special characters in state name.
|
|
351
|
+
i += 1
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
if is_fallback and name[i] == "$":
|
|
355
|
+
# `Fallback` type standart.
|
|
356
|
+
if i == 0:
|
|
357
|
+
i += 1
|
|
358
|
+
found_dollar = True
|
|
359
|
+
continue
|
|
360
|
+
|
|
361
|
+
if i > 0 and not found_dollar:
|
|
362
|
+
settings.warning(
|
|
363
|
+
Strings.COMMAND_FALLBACK_DOLLAR_MISPOSITION,
|
|
364
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
365
|
+
ConfigValuesWarning,
|
|
366
|
+
ConfigValuesError
|
|
367
|
+
)
|
|
368
|
+
found_dollar = True
|
|
369
|
+
name = name[:i] + name[i + 1:]
|
|
370
|
+
i += 1
|
|
371
|
+
continue
|
|
372
|
+
|
|
373
|
+
if found_dollar:
|
|
374
|
+
settings.warning(
|
|
375
|
+
Strings.COMMAND_FALLBACK_DOLLAR_OVERLOAD,
|
|
376
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
377
|
+
ConfigValuesWarning,
|
|
378
|
+
ConfigValuesError
|
|
379
|
+
)
|
|
380
|
+
name = name[:i] + name[i + 1:]
|
|
381
|
+
i += 1
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
settings.warning(
|
|
385
|
+
Strings.COMMAND_NAME_SPECIAL.substitute(character=name[i]),
|
|
386
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
387
|
+
ConfigValuesWarning,
|
|
388
|
+
ConfigValuesError
|
|
389
|
+
)
|
|
390
|
+
removing_indexes.append(i)
|
|
391
|
+
elif name[i].isalnum():
|
|
392
|
+
# Character is valid.
|
|
393
|
+
found_letter = True
|
|
394
|
+
else:
|
|
395
|
+
# Character is unknown.
|
|
396
|
+
settings.warning(
|
|
397
|
+
Strings.UNKNOWN_CHARACTER.substitute(character=name[i]),
|
|
398
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
399
|
+
ConfigValuesWarning,
|
|
400
|
+
ConfigValuesError
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
i += 1
|
|
404
|
+
|
|
405
|
+
# Deleting the removing character.
|
|
406
|
+
for index in reversed(removing_indexes):
|
|
407
|
+
name = name[:index] + name[index + 1:]
|
|
408
|
+
|
|
409
|
+
if is_fallback and not found_dollar:
|
|
410
|
+
# The `Fallback` standart warning.
|
|
411
|
+
settings.warning(
|
|
412
|
+
Strings.COMMAND_FALLBACK_DOLLAR_NOT_FOUND,
|
|
413
|
+
'',
|
|
414
|
+
ConfigValuesWarning,
|
|
415
|
+
warning_levels=(WarningsLevel.BASIC, WarningsLevel.STRICT),
|
|
416
|
+
exception_levels=(None,)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return name
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
def validate_commands(
|
|
423
|
+
settings: Settings,
|
|
424
|
+
config_dict: UnknownConfigType
|
|
425
|
+
) -> Dict[str, CommandClass]:
|
|
426
|
+
"""Validates commands in the config field `commands`.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
settings: The settings.
|
|
430
|
+
config_dict: The config dictionary, which was not validated yet.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Validated commands in stable and known format.
|
|
434
|
+
|
|
435
|
+
Raises:
|
|
436
|
+
ConfigStructureWarning: The `commands` field is not written or not dict.
|
|
437
|
+
ConfigValuesWarning: Command aliases are duplicating or equals original command name.
|
|
438
|
+
|
|
439
|
+
ConfigStructureError: The `commands` field is not written or not dict.
|
|
440
|
+
ConfigValuesError: Command aliases are duplicating or equals original command name.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
commands: Dict[str, CommandClass] = {}
|
|
444
|
+
used_aliases: List[str] = []
|
|
445
|
+
|
|
446
|
+
if "commands" not in config_dict:
|
|
447
|
+
settings.warning(
|
|
448
|
+
Strings.INVALID_CONFIG_COMMANDS,
|
|
449
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
450
|
+
ConfigStructureWarning,
|
|
451
|
+
ConfigStructureError
|
|
452
|
+
)
|
|
453
|
+
return {}
|
|
454
|
+
|
|
455
|
+
if not isinstance(config_dict["commands"], dict):
|
|
456
|
+
settings.warning(
|
|
457
|
+
Strings.INVALID_CONFIG_COMMANDS,
|
|
458
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
459
|
+
ConfigStructureWarning,
|
|
460
|
+
ConfigStructureError
|
|
461
|
+
)
|
|
462
|
+
return {}
|
|
463
|
+
|
|
464
|
+
for key, command_dict in config_dict["commands"].items():
|
|
465
|
+
# Processing a branch of command.
|
|
466
|
+
key = ConfigValidator.validate_name(
|
|
467
|
+
settings,
|
|
468
|
+
key,
|
|
469
|
+
is_fallback=(command_dict.get("type") == "fallback")
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
command: Optional[CommandClass] = ConfigValidator.validate_command(
|
|
473
|
+
settings,
|
|
474
|
+
command_dict
|
|
475
|
+
)
|
|
476
|
+
if command:
|
|
477
|
+
new_aliases: List[str] = []
|
|
478
|
+
|
|
479
|
+
if "aliases" in command_dict:
|
|
480
|
+
for alias in command_dict["aliases"]:
|
|
481
|
+
# Processing an alias in the command aliases.
|
|
482
|
+
alias = ConfigValidator.validate_name(settings, alias)
|
|
483
|
+
|
|
484
|
+
if alias in used_aliases:
|
|
485
|
+
settings.warning(
|
|
486
|
+
Strings.COMMAND_ALIAS_COPIED.substitute(alias=alias),
|
|
487
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
488
|
+
ConfigValuesWarning,
|
|
489
|
+
ConfigValuesError
|
|
490
|
+
)
|
|
491
|
+
elif alias == key:
|
|
492
|
+
settings.warning(
|
|
493
|
+
Strings.COMMAND_ALIAS_REDUNDANCY.substitute(alias=alias),
|
|
494
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
495
|
+
ConfigValuesWarning,
|
|
496
|
+
ConfigValuesError
|
|
497
|
+
)
|
|
498
|
+
else:
|
|
499
|
+
used_aliases.append(alias)
|
|
500
|
+
new_aliases.append(alias)
|
|
501
|
+
|
|
502
|
+
command_dict["aliases"] = new_aliases
|
|
503
|
+
|
|
504
|
+
key = ConfigValidator.validate_name(settings, key)
|
|
505
|
+
commands[key] = command
|
|
506
|
+
|
|
507
|
+
return commands
|
|
508
|
+
|
|
509
|
+
@staticmethod
|
|
510
|
+
def validate_states(
|
|
511
|
+
settings: Settings,
|
|
512
|
+
config_dict: UnknownConfigType,
|
|
513
|
+
commands: Dict[str, CommandClass]
|
|
514
|
+
) -> CommandStates:
|
|
515
|
+
"""Validates config states.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
settings: The settings.
|
|
519
|
+
config_dict: The config dictionary, which was not validated yet.
|
|
520
|
+
commands: The proccessed commands dict.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Validated command states.
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
ConfigStructureWarning: The config has not the field `states` or the states type is not dict.
|
|
527
|
+
ConfigValuesWarning: An internal state by format is not written in `InternalCommandStates` or a command name is unknown.
|
|
528
|
+
|
|
529
|
+
ConfigStructureError: The config has not the field `states` or the states type is not dict.
|
|
530
|
+
ConfigValuesError: An internal state by format is not written in `InternalCommandStates` or a command name is unknown.
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
states: CommandStates = {}
|
|
534
|
+
|
|
535
|
+
command_names: List[str] = [key for key in list(commands.keys())]
|
|
536
|
+
|
|
537
|
+
if not "states" in config_dict:
|
|
538
|
+
settings.warning(
|
|
539
|
+
Strings.INVALID_CONFIG_STATES,
|
|
540
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
541
|
+
ConfigStructureWarning,
|
|
542
|
+
ConfigStructureError
|
|
543
|
+
)
|
|
544
|
+
return {
|
|
545
|
+
"__base__": command_names
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if not isinstance(config_dict["states"], dict):
|
|
549
|
+
settings.warning(
|
|
550
|
+
Strings.INVALID_CONFIG_STATES,
|
|
551
|
+
Strings.AUTO_CORRECT_TO_DEFAULTS,
|
|
552
|
+
ConfigStructureWarning,
|
|
553
|
+
ConfigStructureError
|
|
554
|
+
)
|
|
555
|
+
return {
|
|
556
|
+
"__base__": command_names
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for state, names in config_dict["states"].items():
|
|
560
|
+
# Proccessing a state with their command names.
|
|
561
|
+
state = ConfigValidator.validate_name(settings, state, is_state=True)
|
|
562
|
+
|
|
563
|
+
if (
|
|
564
|
+
state.startswith("__")
|
|
565
|
+
and state.endswith("__")
|
|
566
|
+
and state not in get_args(InternalCommandStates)
|
|
567
|
+
):
|
|
568
|
+
# An internal state is not written in `InternalCommandStates`.
|
|
569
|
+
settings.warning(
|
|
570
|
+
Strings.STATE_INVALID_NAME,
|
|
571
|
+
Strings.AUTO_CORRECT_WITH_RENAMING,
|
|
572
|
+
ConfigValuesWarning,
|
|
573
|
+
ConfigValuesError
|
|
574
|
+
)
|
|
575
|
+
state = state[:2] + state[2:-2]
|
|
576
|
+
|
|
577
|
+
states[state] = []
|
|
578
|
+
|
|
579
|
+
for name in names:
|
|
580
|
+
# Processing a command in the state.
|
|
581
|
+
name = ConfigValidator.validate_name(settings, name)
|
|
582
|
+
|
|
583
|
+
if name not in command_names:
|
|
584
|
+
# Unknown command name.
|
|
585
|
+
settings.warning(
|
|
586
|
+
Strings.COMMAND_UNKNOWN_NAME,
|
|
587
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
588
|
+
ConfigValuesWarning,
|
|
589
|
+
ConfigValuesError
|
|
590
|
+
)
|
|
591
|
+
else:
|
|
592
|
+
states[state].append(name)
|
|
593
|
+
|
|
594
|
+
return states
|
|
595
|
+
|
|
596
|
+
@staticmethod
|
|
597
|
+
def validate_config(settings: Settings, config_dict: UnknownConfigType) -> ConfigType:
|
|
598
|
+
"""Validates a raw config.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
settings: The settings.
|
|
602
|
+
config_dict: The config dictionary, which was not validated yet.
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
Validated config.
|
|
606
|
+
"""
|
|
607
|
+
|
|
608
|
+
version: ConfigVersion = ConfigValidator.validate_version(settings, config_dict)
|
|
609
|
+
commands: Dict[str, CommandClass] = ConfigValidator.validate_commands(settings, config_dict)
|
|
610
|
+
states: CommandStates = ConfigValidator.validate_states(settings, config_dict, commands)
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
"version": version,
|
|
614
|
+
"states": states,
|
|
615
|
+
"commands": commands
|
|
616
|
+
}
|
untils/factories.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""factories.py - All factories."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-few-public-methods
|
|
4
|
+
|
|
5
|
+
from typing import List, Any
|
|
6
|
+
|
|
7
|
+
from untils.utils.type_aliases import CommandType
|
|
8
|
+
|
|
9
|
+
from untils.command import (
|
|
10
|
+
CommandNode, CommandWordNode, CommandFallbackNode, CommandFlagNode, CommandOptionNode, AliasNode
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
class CommandNodeFactory:
|
|
14
|
+
"""Factory class for command nodes."""
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def create(
|
|
18
|
+
cls,
|
|
19
|
+
name: str,
|
|
20
|
+
node_type: CommandType,
|
|
21
|
+
aliases: List[AliasNode],
|
|
22
|
+
default: Any,
|
|
23
|
+
children: List[CommandNode]
|
|
24
|
+
) -> CommandNode:
|
|
25
|
+
"""Creates a command node by a template.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
name: The command name.
|
|
29
|
+
node_type: The command type.
|
|
30
|
+
aliases: The command aliases.
|
|
31
|
+
default: The command default value.
|
|
32
|
+
children: The nest commands.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"word": CommandWordNode(name, node_type, aliases, children),
|
|
37
|
+
"fallback": CommandFallbackNode(name, node_type, default, children),
|
|
38
|
+
"flag": CommandFlagNode(name, node_type, aliases, default),
|
|
39
|
+
"option": CommandOptionNode(name, node_type, aliases, default)
|
|
40
|
+
}[node_type]
|