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
untils/input_token.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""input_token.py - Raw and Final input tokens for `Tokenizer` and `InputValidator`."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-few-public-methods
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from untils.utils.enums import RawTokenType, FinalTokenType
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class RawInputToken:
|
|
13
|
+
"""The raw input token for `Tokenizer`."""
|
|
14
|
+
|
|
15
|
+
type: RawTokenType
|
|
16
|
+
"""The raw command type."""
|
|
17
|
+
value: str
|
|
18
|
+
"""The raw value."""
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
return f"RawInputToken[{self.type.name}](value='{self.value}')"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FinalInputTokenWord:
|
|
26
|
+
"""The word type of `FinalInputToken`."""
|
|
27
|
+
|
|
28
|
+
type: Literal[FinalTokenType.WORD]
|
|
29
|
+
"""The command type. Is literal."""
|
|
30
|
+
value: str
|
|
31
|
+
"""The command value."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, value: str) -> None:
|
|
34
|
+
self.type = FinalTokenType.WORD
|
|
35
|
+
self.value = value
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"FinalInputTokenWord(value={self.value})"
|
|
39
|
+
|
|
40
|
+
def __eq__(self, value: object) -> bool:
|
|
41
|
+
if isinstance(value, FinalInputTokenWord):
|
|
42
|
+
return self.value == value.value
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def __ne__(self, value: object) -> bool:
|
|
46
|
+
if isinstance(value, FinalInputTokenWord):
|
|
47
|
+
return self.value != value.value
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
class FinalInputTokenFlag:
|
|
51
|
+
"""The flag type of `FinalInputToken`."""
|
|
52
|
+
|
|
53
|
+
type: Literal[FinalTokenType.FLAG]
|
|
54
|
+
"""The command type. Is literal."""
|
|
55
|
+
name: str
|
|
56
|
+
"""The command name."""
|
|
57
|
+
value: bool
|
|
58
|
+
"""The command value."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, name: str, value: bool) -> None:
|
|
61
|
+
self.type = FinalTokenType.FLAG
|
|
62
|
+
self.name = name
|
|
63
|
+
self.value = value
|
|
64
|
+
|
|
65
|
+
def __repr__(self) -> str:
|
|
66
|
+
return f"FinalInputTokenFlag(name={self.name}, value={self.value})"
|
|
67
|
+
|
|
68
|
+
def __eq__(self, value: object) -> bool:
|
|
69
|
+
if isinstance(value, FinalInputTokenFlag):
|
|
70
|
+
return self.name == value.name and self.value == value.value
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def __ne__(self, value: object) -> bool:
|
|
74
|
+
if isinstance(value, FinalInputTokenFlag):
|
|
75
|
+
return self.name != value.name or self.value != value.value
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
class FinalInputTokenOption:
|
|
79
|
+
"""The option type of `FinalInputToken`."""
|
|
80
|
+
|
|
81
|
+
type: Literal[FinalTokenType.OPTION]
|
|
82
|
+
"""The command type. Is literal."""
|
|
83
|
+
name: str
|
|
84
|
+
"""The command name."""
|
|
85
|
+
value: Any
|
|
86
|
+
"""The command value."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, name: str, value: Any) -> None:
|
|
89
|
+
self.type = FinalTokenType.OPTION
|
|
90
|
+
self.name = name
|
|
91
|
+
self.value = value
|
|
92
|
+
|
|
93
|
+
def __repr__(self) -> str:
|
|
94
|
+
return f"FinalInputTokenOption(name={self.name}, value={self.value})"
|
|
95
|
+
|
|
96
|
+
def __eq__(self, value: object) -> bool:
|
|
97
|
+
if isinstance(value, FinalInputTokenOption):
|
|
98
|
+
return self.name == value.name and self.value == value.value
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def __ne__(self, value: object) -> bool:
|
|
102
|
+
if isinstance(value, FinalInputTokenOption):
|
|
103
|
+
return self.name != value.name or self.value != value.value
|
|
104
|
+
return True
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
"""input_validator.py - Input validations."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Literal, cast, Union, Dict, Optional
|
|
4
|
+
|
|
5
|
+
from untils.utils.type_aliases import InputDict
|
|
6
|
+
from untils.utils.enums import RawTokenType, FinalTokenType, InternalState
|
|
7
|
+
from untils.utils.constants import Strings
|
|
8
|
+
from untils.utils.protocols import FinalInputProtocol
|
|
9
|
+
|
|
10
|
+
from untils.input_token import (
|
|
11
|
+
RawInputToken, FinalInputTokenWord, FinalInputTokenFlag,
|
|
12
|
+
FinalInputTokenOption
|
|
13
|
+
)
|
|
14
|
+
from untils.settings import Settings
|
|
15
|
+
from untils.utils.lib_warnings import (
|
|
16
|
+
InputStructureWarning, InputValuesWarning, InputStructureError, InputValuesError
|
|
17
|
+
)
|
|
18
|
+
from untils.commands_config import CommandsConfig
|
|
19
|
+
from untils.command import (
|
|
20
|
+
CommandNode, CommandWordNode, CommandFallbackNode, CommandFlagNode, CommandOptionNode
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
class InputValidator:
|
|
24
|
+
"""Validator class for tokenized input."""
|
|
25
|
+
|
|
26
|
+
__slots__ = ["_settings", "_config", "_input_tokens", "_result", "_i"]
|
|
27
|
+
|
|
28
|
+
_settings: Settings
|
|
29
|
+
"""The settings."""
|
|
30
|
+
_config: Optional[CommandsConfig]
|
|
31
|
+
"""The commands config."""
|
|
32
|
+
_input_tokens: List[RawInputToken]
|
|
33
|
+
"""The raw input tokens."""
|
|
34
|
+
_result: List[FinalInputProtocol]
|
|
35
|
+
"""The result of final input tokens."""
|
|
36
|
+
_i: int
|
|
37
|
+
"""The validation index."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
settings: Settings,
|
|
42
|
+
config: Optional[CommandsConfig],
|
|
43
|
+
input_tokens: List[RawInputToken]
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Args:
|
|
47
|
+
settings: The settings.
|
|
48
|
+
config: The validated and parsed commands config.
|
|
49
|
+
input_tokens: The raw input tokens.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
self._settings = settings
|
|
53
|
+
self._config = config
|
|
54
|
+
self._input_tokens = input_tokens
|
|
55
|
+
self._result = []
|
|
56
|
+
self._i = 0
|
|
57
|
+
|
|
58
|
+
def warning_out_of_bounce(self) -> None:
|
|
59
|
+
"""Warnings out of bounce.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
InputStructureWarning: Always.
|
|
63
|
+
|
|
64
|
+
InputStructureError: Always.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
self._settings.warning(
|
|
68
|
+
Strings.END_OF_INPUT,
|
|
69
|
+
Strings.AUTO_CORRECT_WITH_REMOVING,
|
|
70
|
+
InputStructureWarning,
|
|
71
|
+
InputStructureError
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def cast_token(
|
|
75
|
+
self,
|
|
76
|
+
token_object: Union[FinalInputTokenWord, FinalInputTokenFlag, FinalInputTokenOption]
|
|
77
|
+
) -> FinalInputProtocol:
|
|
78
|
+
"""Casts a token to the `FinalInputProtocol`.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
token_object: The token.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The casted token.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
return cast(FinalInputProtocol, token_object)
|
|
88
|
+
|
|
89
|
+
def expect_end(self, offset: int=1) -> None:
|
|
90
|
+
"""Expects an end by offset.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
offset: The number greater than 0, which defines an index offset, that cannot be out of bounce.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
InputStructureWarning: If the offset out of bounce.
|
|
97
|
+
|
|
98
|
+
InputStructureError: If the offset out of bounce.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
if offset <= 0:
|
|
102
|
+
self._settings.warning(
|
|
103
|
+
Strings.NON_POSITIVE_OFFSET,
|
|
104
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
105
|
+
InputValuesWarning,
|
|
106
|
+
InputValuesError
|
|
107
|
+
)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if self._i >= len(self._input_tokens) - offset:
|
|
111
|
+
self._settings.warning(
|
|
112
|
+
Strings.END_OF_INPUT,
|
|
113
|
+
Strings.AUTO_CORRECT_WITH_ACCEPTING,
|
|
114
|
+
InputStructureWarning,
|
|
115
|
+
InputStructureError
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def validate_token_word(self) -> None:
|
|
119
|
+
"""Validates a `Word` token."""
|
|
120
|
+
|
|
121
|
+
token: RawInputToken = self._input_tokens[self._i]
|
|
122
|
+
self._result.append(self.cast_token(FinalInputTokenWord(token.value)))
|
|
123
|
+
self._i += 1
|
|
124
|
+
|
|
125
|
+
def validate_token_flag(self) -> None:
|
|
126
|
+
"""Validates a `Flag` token."""
|
|
127
|
+
|
|
128
|
+
value: bool = True
|
|
129
|
+
|
|
130
|
+
invert_token: RawInputToken = self._input_tokens[self._i]
|
|
131
|
+
if invert_token.type == RawTokenType.NOT:
|
|
132
|
+
# Invert mark.
|
|
133
|
+
self._settings.logger.debug("Process `Not` token.")
|
|
134
|
+
|
|
135
|
+
value = False
|
|
136
|
+
self.expect_end(offset=1) # [...][CURRENT_TOKEN][!LOOKUP!][...]
|
|
137
|
+
self._i += 1
|
|
138
|
+
|
|
139
|
+
if self._input_tokens[self._i].type != RawTokenType.WORD:
|
|
140
|
+
self._settings.warning(
|
|
141
|
+
Strings.EXPECTED_SYNTAX_FLAG,
|
|
142
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
143
|
+
InputStructureWarning,
|
|
144
|
+
InputStructureError
|
|
145
|
+
)
|
|
146
|
+
while (
|
|
147
|
+
self._i <= len(self._input_tokens)
|
|
148
|
+
and self._input_tokens[self._i].type != RawTokenType.WORD
|
|
149
|
+
):
|
|
150
|
+
self._i += 1
|
|
151
|
+
|
|
152
|
+
name_tokens: List[RawInputToken] = self.validate_name_tokens()
|
|
153
|
+
name: str = ""
|
|
154
|
+
|
|
155
|
+
for name_token in name_tokens:
|
|
156
|
+
name += name_token.value
|
|
157
|
+
|
|
158
|
+
if name != "":
|
|
159
|
+
# The flag's name.
|
|
160
|
+
self._settings.logger.debug("Process `Word` token for the flag's name.")
|
|
161
|
+
self._result.append(self.cast_token(FinalInputTokenFlag(name, value)))
|
|
162
|
+
else:
|
|
163
|
+
self._settings.warning(
|
|
164
|
+
Strings.EXPECTED_SYNTAX_FLAG,
|
|
165
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
166
|
+
InputStructureWarning,
|
|
167
|
+
InputStructureError
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def validate_name_tokens(self) -> List[RawInputToken]:
|
|
171
|
+
"""Validates a row of tokens with type `Word` and `String` without spaces as name.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The list of name tokens.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
self._settings.logger.debug("Process name tokens.")
|
|
178
|
+
|
|
179
|
+
current_token: RawInputToken = self._input_tokens[self._i]
|
|
180
|
+
name_tokens: List[RawInputToken] = [current_token]
|
|
181
|
+
|
|
182
|
+
if current_token.type == RawTokenType.STRING:
|
|
183
|
+
return name_tokens
|
|
184
|
+
if current_token.type not in (RawTokenType.WORD, RawTokenType.MINUS):
|
|
185
|
+
self._settings.warning(
|
|
186
|
+
Strings.COMMAND_UNKNOWN_NAME,
|
|
187
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
188
|
+
InputStructureWarning,
|
|
189
|
+
InputStructureError
|
|
190
|
+
)
|
|
191
|
+
self._i += 1
|
|
192
|
+
return name_tokens
|
|
193
|
+
|
|
194
|
+
if self._i == len(self._input_tokens) - 1:
|
|
195
|
+
return name_tokens
|
|
196
|
+
|
|
197
|
+
self.expect_end(offset=1) # [...][CURRENT_TOKEN][!LOOKUP!][...]
|
|
198
|
+
self._i += 1
|
|
199
|
+
|
|
200
|
+
while (
|
|
201
|
+
(self._i < len(self._input_tokens))
|
|
202
|
+
and (self._input_tokens[self._i].type in (RawTokenType.WORD, RawTokenType.MINUS))
|
|
203
|
+
):
|
|
204
|
+
self._settings.logger.debug(f"Process name token: {self._input_tokens[self._i]}.")
|
|
205
|
+
name_tokens.append(self._input_tokens[self._i])
|
|
206
|
+
self._i += 1
|
|
207
|
+
|
|
208
|
+
self._settings.logger.debug(f"Processed name tokens: {name_tokens}.")
|
|
209
|
+
|
|
210
|
+
return name_tokens
|
|
211
|
+
|
|
212
|
+
def validate_token_option(self) -> None:
|
|
213
|
+
"""Validates an `Option` token."""
|
|
214
|
+
|
|
215
|
+
name_tokens: List[RawInputToken] = self.validate_name_tokens()
|
|
216
|
+
name: str = ""
|
|
217
|
+
|
|
218
|
+
for name_token in name_tokens:
|
|
219
|
+
name += name_token.value
|
|
220
|
+
|
|
221
|
+
if name == "":
|
|
222
|
+
self._settings.warning(
|
|
223
|
+
Strings.OPTION_NAME_INVALID,
|
|
224
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
225
|
+
InputStructureWarning,
|
|
226
|
+
InputStructureError
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
self.expect_end(offset=1) # [...][CURRENT_TOKEN][!LOOKUP!][...]
|
|
230
|
+
self._i += 1
|
|
231
|
+
|
|
232
|
+
while (
|
|
233
|
+
self._i < len(self._input_tokens)
|
|
234
|
+
and self._input_tokens[self._i].type == RawTokenType.SPACE
|
|
235
|
+
):
|
|
236
|
+
self._i += 1
|
|
237
|
+
|
|
238
|
+
value_tokens: List[RawInputToken] = self.validate_name_tokens()
|
|
239
|
+
value: str = ""
|
|
240
|
+
|
|
241
|
+
for value_token in value_tokens:
|
|
242
|
+
value += value_token.value
|
|
243
|
+
|
|
244
|
+
if value != "":
|
|
245
|
+
# The option's value.
|
|
246
|
+
self._settings.logger.debug("Process `Word` or `String` token for the option's value")
|
|
247
|
+
self._result.append(self.cast_token(FinalInputTokenOption(name, value)))
|
|
248
|
+
else:
|
|
249
|
+
self._settings.warning(
|
|
250
|
+
Strings.OPTION_VALUE_INVALID,
|
|
251
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
252
|
+
InputStructureWarning,
|
|
253
|
+
InputStructureError
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def validate_token_minus(self) -> None:
|
|
257
|
+
"""Validates a `Minus` token."""
|
|
258
|
+
|
|
259
|
+
start: int = self._i
|
|
260
|
+
|
|
261
|
+
while (
|
|
262
|
+
self._i < len(self._input_tokens)
|
|
263
|
+
and self._input_tokens[self._i].type == RawTokenType.MINUS
|
|
264
|
+
):
|
|
265
|
+
self._i += 1
|
|
266
|
+
|
|
267
|
+
count: int = self._i - start
|
|
268
|
+
expected_type: Literal[FinalTokenType.FLAG, FinalTokenType.OPTION]\
|
|
269
|
+
= FinalTokenType.FLAG if count == 1 else FinalTokenType.OPTION
|
|
270
|
+
|
|
271
|
+
if expected_type == FinalTokenType.FLAG:
|
|
272
|
+
self._settings.logger.debug("Expected `Flag` construction.")
|
|
273
|
+
self.validate_token_flag()
|
|
274
|
+
elif expected_type == FinalTokenType.OPTION:
|
|
275
|
+
self._settings.logger.debug("Expected `Option` construction.")
|
|
276
|
+
self.validate_token_option()
|
|
277
|
+
|
|
278
|
+
def validate_fallback_defaults(self) -> None:
|
|
279
|
+
"""Validates `Fallback` commands with defaults in path if they not written."""
|
|
280
|
+
|
|
281
|
+
result: List[FinalInputProtocol] = []
|
|
282
|
+
commands: List[CommandNode] = self._config.commands if self._config is not None else []
|
|
283
|
+
|
|
284
|
+
found: bool = False
|
|
285
|
+
for part in self._result:
|
|
286
|
+
# Copying.
|
|
287
|
+
found = False
|
|
288
|
+
for node in commands:
|
|
289
|
+
if node.type == "word":
|
|
290
|
+
node = cast(CommandWordNode, node)
|
|
291
|
+
|
|
292
|
+
if not (
|
|
293
|
+
node.name == part.value
|
|
294
|
+
or part.value in [alias.alias_name for alias in node.aliases]
|
|
295
|
+
):
|
|
296
|
+
continue
|
|
297
|
+
elif node.type == "fallback":
|
|
298
|
+
node = cast(CommandFallbackNode, node)
|
|
299
|
+
else:
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
result.append(part)
|
|
303
|
+
commands = node.children
|
|
304
|
+
found = True
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
if not found:
|
|
308
|
+
# Invalid path.
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
while commands != []:
|
|
312
|
+
# Searching `Fallback`s.
|
|
313
|
+
found = False
|
|
314
|
+
|
|
315
|
+
for node in commands:
|
|
316
|
+
if node.type == "fallback":
|
|
317
|
+
node = cast(CommandFallbackNode, node)
|
|
318
|
+
result.append(self.cast_token(FinalInputTokenWord(str(node.default))))
|
|
319
|
+
commands = node.children
|
|
320
|
+
found = True
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
if not found:
|
|
324
|
+
# Invalid path.
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
self._result = result
|
|
328
|
+
|
|
329
|
+
def validate_input(self, settings: Settings) -> List[FinalInputProtocol]:
|
|
330
|
+
"""Validates a user input.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
settings: The settings.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
The list with final validated tokens.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
self._settings.logger.debug(
|
|
340
|
+
f"InputValidator.validate_input(input_tokens='{self._input_tokens}')"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
self._settings = settings
|
|
344
|
+
self._result = []
|
|
345
|
+
self._i = 0
|
|
346
|
+
|
|
347
|
+
while self._i < len(self._input_tokens):
|
|
348
|
+
self._settings.logger.debug(f"New iteration: {self._i}.")
|
|
349
|
+
|
|
350
|
+
token: RawInputToken = self._input_tokens[self._i]
|
|
351
|
+
|
|
352
|
+
if token.type == RawTokenType.WORD:
|
|
353
|
+
self._settings.logger.debug("Process `Word` token.")
|
|
354
|
+
self.validate_token_word()
|
|
355
|
+
|
|
356
|
+
elif token.type == RawTokenType.MINUS:
|
|
357
|
+
self._settings.logger.debug("Process `Minus` token.")
|
|
358
|
+
self.validate_token_minus()
|
|
359
|
+
|
|
360
|
+
elif token.type == RawTokenType.SPACE:
|
|
361
|
+
self._settings.logger.debug("Process `Space` token.")
|
|
362
|
+
|
|
363
|
+
elif token.type == RawTokenType.STRING:
|
|
364
|
+
self._settings.logger.debug("Process `String` token.")
|
|
365
|
+
self._result.append(self.cast_token(FinalInputTokenWord(token.value)))
|
|
366
|
+
|
|
367
|
+
else:
|
|
368
|
+
self._settings.logger.debug(f"Process unknown token: {token}.")
|
|
369
|
+
self._settings.warning(
|
|
370
|
+
Strings.UNKNOWN_TOKEN,
|
|
371
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
372
|
+
InputStructureWarning,
|
|
373
|
+
InputStructureError
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
self._i += 1
|
|
377
|
+
|
|
378
|
+
self.validate_fallback_defaults()
|
|
379
|
+
|
|
380
|
+
return self._result
|
|
381
|
+
|
|
382
|
+
class ParsedInputValidator:
|
|
383
|
+
"""Validator class for parsed input dict."""
|
|
384
|
+
|
|
385
|
+
@staticmethod
|
|
386
|
+
def validate_commands_path(
|
|
387
|
+
settings: Settings,
|
|
388
|
+
input_dict: InputDict,
|
|
389
|
+
config: CommandsConfig
|
|
390
|
+
) -> bool:
|
|
391
|
+
"""Validates a commands path, flags and options.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
settings: The settings.
|
|
395
|
+
input_dict: The parsed input dictionary.
|
|
396
|
+
config: The parsed commands config.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
`True` if commands path, flags and options is valid, else `False`.
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
InputValuesWarning: If the first command is not written in current state in the settings or path cannot be accessed to the input path.
|
|
403
|
+
|
|
404
|
+
InputValuesError: If the first command is not written in current state in the settings or path cannot be accessed to the input path.
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
flag_keys: List[str] = list(input_dict["flags"].keys())
|
|
408
|
+
option_keys: List[str] = list(input_dict["options"].keys())
|
|
409
|
+
validated_flags: Dict[str, bool] = {n: False for n in flag_keys}
|
|
410
|
+
validated_options: Dict[str, bool] = {n: False for n in option_keys}
|
|
411
|
+
|
|
412
|
+
def validate_flags(children: List[CommandNode]) -> None:
|
|
413
|
+
"""Validates the flags.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
children: The commands.
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
for command in children:
|
|
420
|
+
if command.type == "flag":
|
|
421
|
+
command = cast(CommandFlagNode, command)
|
|
422
|
+
validated_flags[command.name] = True
|
|
423
|
+
for alias in command.aliases:
|
|
424
|
+
validated_flags[alias.alias_name] = True
|
|
425
|
+
|
|
426
|
+
def validate_options(children: List[CommandNode]) -> None:
|
|
427
|
+
"""Validates the options.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
children: The commands.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
for command in children:
|
|
434
|
+
if command.type == "option":
|
|
435
|
+
command = cast(CommandOptionNode, command)
|
|
436
|
+
validated_options[command.name] = True
|
|
437
|
+
for alias in command.aliases:
|
|
438
|
+
validated_options[alias.alias_name] = True
|
|
439
|
+
|
|
440
|
+
def validate_command(children: List[CommandNode], i: int) -> bool:
|
|
441
|
+
"""Validates a command recursively.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
children: The commands.
|
|
445
|
+
i: Current path index.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
`False` if command not in current state (i == 0 -> First command) or has not children, else `True`.
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
InputValuesWarning: If the first command is not written in current state in the settings or no commands in children in path.
|
|
452
|
+
|
|
453
|
+
InputValuesError: If the first command is not written in current state in the settings or no commands in children in path.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
for command in children:
|
|
457
|
+
if command.type not in ("word", "fallback"):
|
|
458
|
+
# Type filtration for positioned commands.
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
command = cast(Union[CommandWordNode, CommandFallbackNode], command)
|
|
462
|
+
|
|
463
|
+
if command.type == "word":
|
|
464
|
+
# Conditions:
|
|
465
|
+
# 1. `command.type == "word"`` -> Shall equals with original command key or with alias.
|
|
466
|
+
# 2. `command.type == "fallback"` -> None (accepts any word).
|
|
467
|
+
command = cast(CommandWordNode, command)
|
|
468
|
+
|
|
469
|
+
if input_dict["path"][i] == command.name:
|
|
470
|
+
# Continue current iteration.
|
|
471
|
+
pass
|
|
472
|
+
elif input_dict["path"][i] not in [
|
|
473
|
+
alias.alias_name for alias in command.aliases
|
|
474
|
+
]:
|
|
475
|
+
# Next iteration.
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
if i == 0:
|
|
479
|
+
# First iteration must has root command.
|
|
480
|
+
in_state: bool = False
|
|
481
|
+
for state_node in config.states:
|
|
482
|
+
if state_node.is_internal:
|
|
483
|
+
if (
|
|
484
|
+
state_node.name == InternalState.BASE.value
|
|
485
|
+
and command.name in state_node.commands
|
|
486
|
+
):
|
|
487
|
+
# First state in `__base__` state, which defines any current state.
|
|
488
|
+
in_state = True
|
|
489
|
+
break
|
|
490
|
+
if settings.current_state == state_node.name:
|
|
491
|
+
# Command defined in current state.
|
|
492
|
+
in_state = command.name in state_node.commands
|
|
493
|
+
break
|
|
494
|
+
|
|
495
|
+
if not in_state:
|
|
496
|
+
# First iteration not in states.
|
|
497
|
+
settings.warning(
|
|
498
|
+
Strings.COMMAND_NOT_IN_CURRENT_STATE.substitute(
|
|
499
|
+
state=settings.current_state
|
|
500
|
+
),
|
|
501
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
502
|
+
InputValuesWarning,
|
|
503
|
+
InputValuesError
|
|
504
|
+
)
|
|
505
|
+
return False
|
|
506
|
+
|
|
507
|
+
validate_flags(command.children)
|
|
508
|
+
validate_options(command.children)
|
|
509
|
+
|
|
510
|
+
if i < len(input_dict["path"]) - 1:
|
|
511
|
+
validate_command(command.children, i + 1)
|
|
512
|
+
|
|
513
|
+
return True
|
|
514
|
+
|
|
515
|
+
settings.warning(
|
|
516
|
+
Strings.INPUT_PATH_INVALID.substitute(name=input_dict["path"][i]),
|
|
517
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
518
|
+
InputValuesWarning,
|
|
519
|
+
InputValuesError
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
return False
|
|
523
|
+
|
|
524
|
+
if input_dict["path"] == []:
|
|
525
|
+
# Current path is empty.
|
|
526
|
+
validate_flags(config.commands)
|
|
527
|
+
validate_options(config.commands)
|
|
528
|
+
|
|
529
|
+
return all(validated_flags.values()) and all(validated_options.values())
|
|
530
|
+
|
|
531
|
+
if not validate_command(config.commands, 0):
|
|
532
|
+
return False
|
|
533
|
+
|
|
534
|
+
validate_flags(config.commands)
|
|
535
|
+
validate_options(config.commands)
|
|
536
|
+
|
|
537
|
+
return all(validated_flags.values()) and all(validated_options.values())
|
|
538
|
+
|
|
539
|
+
@staticmethod
|
|
540
|
+
def validate_input_dict(
|
|
541
|
+
settings: Settings,
|
|
542
|
+
input_dict: InputDict,
|
|
543
|
+
config: CommandsConfig
|
|
544
|
+
) -> bool:
|
|
545
|
+
"""Validates input dict with current context in settings.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
settings: The settings.
|
|
549
|
+
input_dict: The input dictionary.
|
|
550
|
+
config: The parsed commands config.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
`True` if the input is valid, else `False`.
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
return ParsedInputValidator.validate_commands_path(settings, input_dict, config)
|