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/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""*untils* - Is light-weight library for console games or small utils to process user input as commands and describe structure with config."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnusedImport=false
|
|
4
|
+
# ^^^^^^^ (Public imports.)
|
|
5
|
+
|
|
6
|
+
from untils import utils
|
|
7
|
+
|
|
8
|
+
from untils.command_system import CommandSystem
|
|
9
|
+
from untils.command import (
|
|
10
|
+
AliasNode, CommandNode, CommandWordNode, CommandFallbackNode, CommandFlagNode,
|
|
11
|
+
CommandOptionNode, StateNode
|
|
12
|
+
)
|
|
13
|
+
from untils.commands_config import CommandsConfig
|
|
14
|
+
from untils.config_validator import ConfigValidator
|
|
15
|
+
from untils.factories import CommandNodeFactory
|
|
16
|
+
from untils.input_token import (
|
|
17
|
+
RawInputToken, FinalInputTokenWord, FinalInputTokenFlag, FinalInputTokenOption
|
|
18
|
+
)
|
|
19
|
+
from untils.input_validator import InputValidator, ParsedInputValidator
|
|
20
|
+
from untils.ioreader import IOReader
|
|
21
|
+
from untils.iovalidator import IOValidator
|
|
22
|
+
from untils.parser import Parser
|
|
23
|
+
from untils.processor import Processor
|
|
24
|
+
from untils.settings import Settings
|
|
25
|
+
from untils.tokenizer import Tokenizer
|
|
26
|
+
|
|
27
|
+
__version__ = "1.0.0"
|
|
28
|
+
__author__ = "BesBobowyy"
|
untils/command.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""command.py - Command nodes."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Any
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from untils.utils.type_aliases import CommandType
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class AliasNode():
|
|
11
|
+
"""Command alias name container."""
|
|
12
|
+
|
|
13
|
+
original_name: str
|
|
14
|
+
"""Original command name."""
|
|
15
|
+
alias_name: str
|
|
16
|
+
"""Alias name."""
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return f"AliasNode('{self.original_name}' -> '{self.alias_name}')"
|
|
20
|
+
|
|
21
|
+
def __eq__(self, value: object) -> bool:
|
|
22
|
+
if isinstance(value, AliasNode):
|
|
23
|
+
return self.original_name == value.original_name and self.alias_name == value.alias_name
|
|
24
|
+
if isinstance(value, CommandNode):
|
|
25
|
+
if value.type == "fallback":
|
|
26
|
+
return False
|
|
27
|
+
return self.original_name == value.name
|
|
28
|
+
if isinstance(value, str):
|
|
29
|
+
return self.original_name == value and self.alias_name == value
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
def __ne__(self, value: object) -> bool:
|
|
33
|
+
if isinstance(value, AliasNode):
|
|
34
|
+
return self.original_name != value.original_name or self.alias_name != value.alias_name
|
|
35
|
+
if isinstance(value, CommandNode):
|
|
36
|
+
if value.type == "fallback":
|
|
37
|
+
return True
|
|
38
|
+
return self.original_name != value.name
|
|
39
|
+
if isinstance(value, str):
|
|
40
|
+
return self.original_name != value or self.alias_name != value
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class CommandNode:
|
|
45
|
+
"""Universal template for command nodes."""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
"""Command name."""
|
|
49
|
+
type: CommandType
|
|
50
|
+
"""Command type."""
|
|
51
|
+
|
|
52
|
+
def __eq__(self, value: object) -> bool:
|
|
53
|
+
if isinstance(value, CommandNode):
|
|
54
|
+
return self.name == value.name and self.type == value.type
|
|
55
|
+
if isinstance(value, str):
|
|
56
|
+
if self.type != "fallback":
|
|
57
|
+
return self.name == value
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def __ne__(self, value: object) -> bool:
|
|
62
|
+
if isinstance(value, CommandNode):
|
|
63
|
+
return self.name != value.name or self.type != value.type
|
|
64
|
+
if isinstance(value, str):
|
|
65
|
+
if self.type != "fallback":
|
|
66
|
+
return self.name != value
|
|
67
|
+
return False
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class CommandWordNode(CommandNode):
|
|
72
|
+
"""The word command type."""
|
|
73
|
+
|
|
74
|
+
aliases: List[AliasNode]
|
|
75
|
+
"""Command aliases."""
|
|
76
|
+
children: List[CommandNode]
|
|
77
|
+
"""Next commands below this."""
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
return f"CommandWordNode[{self.name} : {self.aliases}]{self.children}"
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class CommandFallbackNode(CommandNode):
|
|
84
|
+
"""Command node for fallback type."""
|
|
85
|
+
|
|
86
|
+
default: str
|
|
87
|
+
"""A default value."""
|
|
88
|
+
children: List[CommandNode]
|
|
89
|
+
"""Next commands below this."""
|
|
90
|
+
|
|
91
|
+
def __str__(self) -> str:
|
|
92
|
+
return f"CommandFallbackNode[{self.name}](default='{self.default}'){self.children}"
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class CommandFlagNode(CommandNode):
|
|
96
|
+
"""Command node for flag type."""
|
|
97
|
+
|
|
98
|
+
aliases: List[AliasNode]
|
|
99
|
+
"""Command aliases."""
|
|
100
|
+
default: Any
|
|
101
|
+
"""A default value."""
|
|
102
|
+
|
|
103
|
+
def __str__(self) -> str:
|
|
104
|
+
return f"CommandFlagNode[{self.name} : {self.aliases}](default={repr(self.default)})"
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class CommandOptionNode(CommandNode):
|
|
108
|
+
"""Command node for option type."""
|
|
109
|
+
|
|
110
|
+
aliases: List[AliasNode]
|
|
111
|
+
"""Command aliases."""
|
|
112
|
+
default: Any
|
|
113
|
+
"""A default value."""
|
|
114
|
+
|
|
115
|
+
def __str__(self) -> str:
|
|
116
|
+
return f"CommandOptionNode[{self.name} : {self.aliases}](default={repr(self.default)})"
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class StateNode:
|
|
120
|
+
"""A state node in config."""
|
|
121
|
+
|
|
122
|
+
name: str
|
|
123
|
+
"""The state name."""
|
|
124
|
+
is_internal: bool
|
|
125
|
+
"""Is internal state, which typed in the format `__{name}__`."""
|
|
126
|
+
commands: List[str]
|
|
127
|
+
"""Allowed commands by this state."""
|
|
128
|
+
|
|
129
|
+
def __str__(self) -> str:
|
|
130
|
+
return f"StateNode[{'!' if self.is_internal else ''}{self.name}]{self.commands}"
|
untils/command_system.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""command_system.py - Command system."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnnecessaryIsInstance=false
|
|
4
|
+
|
|
5
|
+
from typing import Optional, List, Union, cast, Dict, Tuple
|
|
6
|
+
|
|
7
|
+
from untils.utils.type_aliases import InputDict, CommandPath, CallableCommand, CommandHistory
|
|
8
|
+
from untils.utils.constants import Strings
|
|
9
|
+
|
|
10
|
+
from untils.commands_config import CommandsConfig
|
|
11
|
+
from untils.settings import Settings
|
|
12
|
+
from untils.processor import Processor
|
|
13
|
+
from untils.input_validator import ParsedInputValidator
|
|
14
|
+
from untils.command import CommandNode, CommandWordNode, CommandFallbackNode
|
|
15
|
+
|
|
16
|
+
class CommandSystem:
|
|
17
|
+
"""Core class with command config, API, processing and much more."""
|
|
18
|
+
|
|
19
|
+
__slots__ = ["settings", "config", "route", "history"]
|
|
20
|
+
|
|
21
|
+
settings: Settings
|
|
22
|
+
config: Optional[CommandsConfig]
|
|
23
|
+
route: Dict[CommandPath, CallableCommand]
|
|
24
|
+
history: CommandHistory
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
settings: Settings,
|
|
29
|
+
config: Optional[CommandsConfig]=None,
|
|
30
|
+
history: Optional[CommandHistory]=None
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Args:
|
|
34
|
+
settings: A `Settings` object as context.
|
|
35
|
+
config: A `Config` object as configuration.
|
|
36
|
+
history: A command history object.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
self.settings = settings
|
|
40
|
+
self.config = config
|
|
41
|
+
self.route = {}
|
|
42
|
+
self.history = {
|
|
43
|
+
"max_size": 100,
|
|
44
|
+
"is_write_overflow": True,
|
|
45
|
+
"notes": []
|
|
46
|
+
} if history is None else history
|
|
47
|
+
|
|
48
|
+
def is_config_loaded(self) -> bool:
|
|
49
|
+
"""Returns a `bool` value, what determines is config loaded."""
|
|
50
|
+
|
|
51
|
+
return self.config is not None
|
|
52
|
+
|
|
53
|
+
def load_config(self, config_path: str) -> None:
|
|
54
|
+
"""Loads a `CommandsConfig` object.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
config_path: Path of config file.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
self.config = Processor.load_config(self.settings, config_path)
|
|
61
|
+
|
|
62
|
+
def set_config(self, config: Optional[CommandsConfig]) -> None:
|
|
63
|
+
"""Sets an already processed config or deletes exist.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
config: A `Config` object or `None`.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
self.config = config
|
|
70
|
+
|
|
71
|
+
def process_input(self, input_str: str) -> InputDict:
|
|
72
|
+
"""Processes a user input and returns `InputDict` as input representation.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
input_str: Input raw string.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
An input representation.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return Processor.process_input(self.settings, self.config, input_str)
|
|
82
|
+
|
|
83
|
+
def is_input_valid(self, input_dict: InputDict) -> bool:
|
|
84
|
+
"""Validates an input.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
input_dict: A cached input for validation.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
`False` if an input is not valid or config not loaded. `True` if an input is valid.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if self.config is not None:
|
|
94
|
+
return ParsedInputValidator.validate_input_dict(self.settings, input_dict, self.config)
|
|
95
|
+
|
|
96
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
97
|
+
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def get_normalized_path(self, input_dict: InputDict) -> List[str]:
|
|
101
|
+
"""Returns original key-names in config by `path` in `input_dict`.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
input_dict: A cached input for validation.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Normalized path from original keys in config.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
if self.config is None:
|
|
111
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
input_path: List[str] = input_dict["path"]
|
|
115
|
+
commands: List[CommandNode] = self.config.commands
|
|
116
|
+
result: List[str] = []
|
|
117
|
+
|
|
118
|
+
self.settings.logger.info(Strings.LOG_CALCULATE_NORMALIZED_PATH_START)
|
|
119
|
+
|
|
120
|
+
for part in input_path:
|
|
121
|
+
for command in commands:
|
|
122
|
+
if command.type == "word":
|
|
123
|
+
command = cast(CommandWordNode, command)
|
|
124
|
+
if (
|
|
125
|
+
part == command.name
|
|
126
|
+
or part in [alias.alias_name for alias in command.aliases]
|
|
127
|
+
):
|
|
128
|
+
result.append(command.name)
|
|
129
|
+
commands = command.children
|
|
130
|
+
break
|
|
131
|
+
elif command.type == "fallback":
|
|
132
|
+
command = cast(CommandFallbackNode, command)
|
|
133
|
+
result.append(command.name)
|
|
134
|
+
commands = command.children
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
self.settings.logger.info(Strings.LOG_CALCULATE_NORMALIZED_PATH_END)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
def get_all_commands(self) -> List[CommandNode]:
|
|
142
|
+
"""Returns all commands as command nodes."""
|
|
143
|
+
|
|
144
|
+
if self.config is None:
|
|
145
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
result: List[CommandNode] = []
|
|
149
|
+
|
|
150
|
+
for command in self.config.commands:
|
|
151
|
+
if command.type in ("word", "fallback"):
|
|
152
|
+
result.append(command)
|
|
153
|
+
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
def get_all_commands_str(self) -> List[str]:
|
|
157
|
+
"""Returns all commands as name strings."""
|
|
158
|
+
|
|
159
|
+
if self.config is None:
|
|
160
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
result: List[str] = []
|
|
164
|
+
|
|
165
|
+
for command in self.config.commands:
|
|
166
|
+
if command.type in ("word", "fallback"):
|
|
167
|
+
result.append(command.name)
|
|
168
|
+
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
def get_available_commands(self) -> List[CommandNode]:
|
|
172
|
+
"""Returns all available commands in current state as command nodes."""
|
|
173
|
+
|
|
174
|
+
if self.config is None:
|
|
175
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
176
|
+
return []
|
|
177
|
+
|
|
178
|
+
result: List[CommandNode] = []
|
|
179
|
+
|
|
180
|
+
for command in self.config.commands:
|
|
181
|
+
if command.type in ("word", "fallback"):
|
|
182
|
+
for state in self.config.states:
|
|
183
|
+
if command.name in state.commands:
|
|
184
|
+
result.append(command)
|
|
185
|
+
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
def get_available_commands_str(self) -> List[str]:
|
|
189
|
+
"""Returns all available commands in current state as name strings."""
|
|
190
|
+
|
|
191
|
+
if self.config is None:
|
|
192
|
+
self.settings.logger.warning(Strings.LOG_CONFIG_NOT_LOADED)
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
result: List[str] = []
|
|
196
|
+
|
|
197
|
+
for command in self.config.commands:
|
|
198
|
+
if command.type in ("word", "fallback"):
|
|
199
|
+
for state in self.config.states:
|
|
200
|
+
if command.name in state.commands:
|
|
201
|
+
result.append(command.name)
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def access_path(
|
|
206
|
+
self,
|
|
207
|
+
input_dict: Union[InputDict, List[str]],
|
|
208
|
+
path: CommandPath,
|
|
209
|
+
is_inclusive: bool=True
|
|
210
|
+
) -> bool:
|
|
211
|
+
"""Validates command path by input.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
input_dict: A parsed input dict. Accepts `InputDict` for a path and `List[str]` only for a path.
|
|
215
|
+
path: A command path, which determines all posible correct ways in path.
|
|
216
|
+
is_inclusive: Always returns `False` if length of two paths are different. This argument changes validation mode: `False` (determines any command input from deferred path) and `True` (determines a single variant for command tree branching).
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
`False` if input path is mispath. `True` if input path equals the deferred path.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
if isinstance(input_dict, dict):
|
|
223
|
+
input_path: List[str] = input_dict["path"]
|
|
224
|
+
elif isinstance(input_dict, list):
|
|
225
|
+
input_path: List[str] = input_dict
|
|
226
|
+
else:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
if is_inclusive and len(path) != len(input_path):
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
i: int = 0
|
|
233
|
+
for part in path:
|
|
234
|
+
if i >= len(input_path):
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
if isinstance(part, str):
|
|
238
|
+
if part in (input_path[i], "-any"):
|
|
239
|
+
i += 1
|
|
240
|
+
continue
|
|
241
|
+
elif isinstance(part, (list, tuple)):
|
|
242
|
+
if input_path[i] in part:
|
|
243
|
+
i += 1
|
|
244
|
+
continue
|
|
245
|
+
return False
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
def get_command(self, path: CommandPath) -> Optional[CallableCommand]:
|
|
249
|
+
"""Get a command from the command routing.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
path: A command path, which determines all posible correct ways in path.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
`CallableCommand` if a function was found by a path. `None` if a function was not found.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
return self.route.get(path)
|
|
259
|
+
|
|
260
|
+
def register_command(self, path: CommandPath, func: CallableCommand) -> bool:
|
|
261
|
+
"""Registers a command from the command routing.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
path: A command path, which determines all posible correct ways in path.
|
|
265
|
+
func: A command implementation as function.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
`False` if path in the command routing. `True` if path not in the command routing and was added.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
if path in self.route:
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
self.route[path] = func
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def change_command(self, path: CommandPath, func: CallableCommand) -> bool:
|
|
278
|
+
"""Changes already registered command in the command routing.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
path: A command path, which determines all posible correct ways in path.
|
|
282
|
+
func: A command implementation as function.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
`False` if path not in the command routing. `True` if path in the command routing and was changed.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
if path not in self.route:
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
self.route[path] = func
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
def unload_command(self, path: CommandPath) -> bool:
|
|
295
|
+
"""Removes already registered command from the command routing.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
path: A command path, which determines all posible correct ways in path.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
`False` if path not in the command routing. `True` if path in the command routing and was deleted.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
if path not in self.route:
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
del self.route[path]
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
def get_history(self) -> List[Tuple[str, InputDict]]:
|
|
311
|
+
"""Returns all notes from command history.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List from tuples with input string and parsed input dict.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
return self.history["notes"]
|
|
318
|
+
|
|
319
|
+
def get_history_input(self) -> List[str]:
|
|
320
|
+
"""Returns all input strings from command history.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List with input string.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
return [note[0] for note in self.history["notes"]]
|
|
327
|
+
|
|
328
|
+
def get_history_dict(self) -> List[InputDict]:
|
|
329
|
+
"""Returns all parsed input dicts from command history.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List with parsed input dict.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
return [note[1] for note in self.history["notes"]]
|
|
336
|
+
|
|
337
|
+
def write_history(self, input_str: str, input_dict: InputDict) -> bool:
|
|
338
|
+
"""Writes a new note to command history.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
input_str: Original input string.
|
|
342
|
+
input_dict: Parsed input dict.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
`False` if max note count limit reached and overwrite disabled, else `True`.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
if (
|
|
349
|
+
len(self.history["notes"]) >= self.history["max_size"]
|
|
350
|
+
and not self.history["is_write_overflow"]
|
|
351
|
+
):
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
self.history["notes"].append((input_str, input_dict))
|
|
355
|
+
return True
|
|
356
|
+
|
|
357
|
+
def read_history(self, index: int) -> Tuple[str, InputDict]:
|
|
358
|
+
"""Returns a note by index in saved indexes.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
index: Positive note index by latest. Will clamped to limits.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
A note from history.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
return self.history["notes"][max(min(0, index), self.history["max_size"] - 1)]
|
|
368
|
+
|
|
369
|
+
def execute(
|
|
370
|
+
self,
|
|
371
|
+
input_str: str,
|
|
372
|
+
input_dict: InputDict,
|
|
373
|
+
normalized_path: List[str],
|
|
374
|
+
tracking: bool=True
|
|
375
|
+
) -> bool:
|
|
376
|
+
"""Executes an input string with the command routing.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
input_str: An input string.
|
|
380
|
+
input_dict: A cached input for validation.
|
|
381
|
+
normalized_path: A command path, which determines all posible correct ways in path.
|
|
382
|
+
tracking: Is save current command in history.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
`False` if a command is not written or not in the command routing. `True` if a command was called.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
if tracking:
|
|
389
|
+
self.write_history(input_str, input_dict)
|
|
390
|
+
|
|
391
|
+
if len(normalized_path) == 0:
|
|
392
|
+
self.settings.logger.info(Strings.COMMAND_NOT_WRITTEN)
|
|
393
|
+
return False
|
|
394
|
+
|
|
395
|
+
for path, func in self.route.items():
|
|
396
|
+
if self.access_path(normalized_path, path, False):
|
|
397
|
+
func(input_str, input_dict)
|
|
398
|
+
return True
|
|
399
|
+
|
|
400
|
+
self.settings.logger.warning(
|
|
401
|
+
Strings.COMMAND_NOT_IMPLEMENTED.substitute(input_str=input_str)
|
|
402
|
+
)
|
|
403
|
+
return False
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""commands_config.py - Commands config."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from untils.utils.type_aliases import ConfigVersion
|
|
8
|
+
|
|
9
|
+
from untils.command import StateNode, CommandNode
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class CommandsConfig:
|
|
13
|
+
"""Configuration class."""
|
|
14
|
+
|
|
15
|
+
version: ConfigVersion
|
|
16
|
+
"""The configuration version. All supported versions are defined in `ConfigVersions`. The latest version is defined in `Constants.LATEST_CONFIG_VERSION`."""
|
|
17
|
+
states: List[StateNode]
|
|
18
|
+
"""All written states. Use states for context separation."""
|
|
19
|
+
commands: List[CommandNode]
|
|
20
|
+
"""All available commands for user."""
|
|
21
|
+
|
|
22
|
+
def __str__(self) -> str:
|
|
23
|
+
return f"CommandsConfig(version={self.version}, states={self.states}, commands={self.commands})"
|