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/ioreader.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""ioreader.py - IO operations through `IOReader` and their mixins for config extensions."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-few-public-methods
|
|
4
|
+
|
|
5
|
+
from typing import Dict, override
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from untils.utils.type_aliases import UnknownConfigType, ConfigSupportedExtensions
|
|
11
|
+
from untils.iovalidator import IOValidator
|
|
12
|
+
from untils.utils.protocols import IOReaderMixin, IOReaderProtocol
|
|
13
|
+
|
|
14
|
+
from untils.settings import Settings
|
|
15
|
+
|
|
16
|
+
class JSONMixin(IOReaderMixin):
|
|
17
|
+
"""The JSON extension for `IOReader`."""
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
@staticmethod
|
|
21
|
+
def read(settings: Settings, file_path: str) -> UnknownConfigType:
|
|
22
|
+
with open(file_path, 'r', encoding='utf-8') as file:
|
|
23
|
+
content: UnknownConfigType = json.loads(file.read())
|
|
24
|
+
return content
|
|
25
|
+
|
|
26
|
+
class IOReader():
|
|
27
|
+
"""Reader class for config loading by paths and settings."""
|
|
28
|
+
|
|
29
|
+
_MIXINS: Dict[ConfigSupportedExtensions, IOReaderProtocol] = {
|
|
30
|
+
".json": JSONMixin,
|
|
31
|
+
".json5": JSONMixin
|
|
32
|
+
}
|
|
33
|
+
"""All supported mixins for `IOReader`."""
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def read_file(settings: Settings, file_path: str) -> UnknownConfigType:
|
|
37
|
+
"""Reads a config file by path.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
settings: The settings.
|
|
41
|
+
file_path: The file path.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The raw unvalidated config.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
IOValidator.validate_config_path(settings, file_path)
|
|
48
|
+
|
|
49
|
+
extension: str = os.path.splitext(file_path)[1]
|
|
50
|
+
content: UnknownConfigType = {}
|
|
51
|
+
|
|
52
|
+
if extension in IOReader._MIXINS:
|
|
53
|
+
content: UnknownConfigType = IOReader._MIXINS[extension].read(settings, file_path)
|
|
54
|
+
|
|
55
|
+
return content
|
untils/iovalidator.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""iovalidator.py - `IOValidator` for IO validations."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-few-public-methods
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from untils.utils.enums import WarningsLevel
|
|
8
|
+
from untils.utils.constants import Constants, Strings
|
|
9
|
+
from untils.utils.lib_warnings import FileWarning, FileError
|
|
10
|
+
|
|
11
|
+
from untils.settings import Settings
|
|
12
|
+
|
|
13
|
+
class IOValidator:
|
|
14
|
+
"""Validator class for the all IO operations for `IOReader`."""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def validate_config_path(settings: Settings, file_path: str) -> bool:
|
|
18
|
+
"""Validates a config path.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
settings: The settings.
|
|
22
|
+
file_path: The file path.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
`True` if the config path is valid and supported, else `False`.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
FileWarning: The config file is not exists or the config extension is not in standart.
|
|
29
|
+
|
|
30
|
+
FileError: The config file is not exists.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
34
|
+
# Processing the config file path.
|
|
35
|
+
extension: str = os.path.splitext(file_path)[1]
|
|
36
|
+
if extension in Constants.SUPPORTED_CONFIG_FORMATS:
|
|
37
|
+
if extension not in Constants.STANDART_CONFIG_FORMATS:
|
|
38
|
+
# The config format is not in standart.
|
|
39
|
+
settings.warning(
|
|
40
|
+
Strings.CONFIG_EXTENSION_NOT_SUPPORTS,
|
|
41
|
+
Strings.AUTO_CORRECT_WITH_ACCEPTING,
|
|
42
|
+
FileWarning,
|
|
43
|
+
warning_levels=(WarningsLevel.BASIC, WarningsLevel.STRICT),
|
|
44
|
+
exception_levels=()
|
|
45
|
+
)
|
|
46
|
+
return True
|
|
47
|
+
else:
|
|
48
|
+
settings.warning(
|
|
49
|
+
Strings.CONFIG_FILE_NOT_EXISTS,
|
|
50
|
+
Strings.AUTO_CORRECT_WITH_SKIPPING,
|
|
51
|
+
FileWarning,
|
|
52
|
+
FileError
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return False
|
untils/parser.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""parser.py - Parses config and input."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Any, cast, get_args, Optional
|
|
4
|
+
|
|
5
|
+
from untils.utils.type_aliases import (
|
|
6
|
+
CommandClass, CommandType, ConfigType, InternalCommandStates, InputDict
|
|
7
|
+
)
|
|
8
|
+
from untils.utils.protocols import FinalInputProtocol
|
|
9
|
+
from untils.utils.enums import FinalTokenType
|
|
10
|
+
|
|
11
|
+
from untils.command import CommandNode, AliasNode, StateNode
|
|
12
|
+
from untils.factories import CommandNodeFactory
|
|
13
|
+
from untils.commands_config import CommandsConfig
|
|
14
|
+
from untils.input_token import FinalInputTokenWord, FinalInputTokenFlag, FinalInputTokenOption
|
|
15
|
+
from untils.settings import Settings
|
|
16
|
+
|
|
17
|
+
class Parser:
|
|
18
|
+
"""This class parses raw data to intermediate reference."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def parse_commands(commands: Dict[str, CommandClass]) -> List[CommandNode]:
|
|
22
|
+
"""Parses a command dictionary to the AST tree of command nodes.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
commands: The commands dictionary.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Parsed AST tree of the command nodes.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def parse_command(name: str, command_dict: CommandClass) -> CommandNode:
|
|
32
|
+
"""Parses a single command.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
name: The command name.
|
|
36
|
+
command_dict: The command dictionary.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The command node.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
command_type: CommandType = command_dict.get("type")
|
|
43
|
+
aliases: List[AliasNode] = []
|
|
44
|
+
default: Any = None
|
|
45
|
+
children: List[CommandNode] = []
|
|
46
|
+
|
|
47
|
+
if command_type in ("word", "flag", "option"):
|
|
48
|
+
aliases = [AliasNode(name, alias) for alias in command_dict.get("aliases", [])]
|
|
49
|
+
|
|
50
|
+
if command_type in ("flag", "option"):
|
|
51
|
+
default: Any = command_dict.get("default", None)
|
|
52
|
+
|
|
53
|
+
if command_type in ("word", "fallback"):
|
|
54
|
+
children: List[CommandNode] = []
|
|
55
|
+
|
|
56
|
+
for child_name, child_dict in command_dict.get("children", {}).items():
|
|
57
|
+
children.append(parse_command(child_name, cast(CommandClass, child_dict)))
|
|
58
|
+
|
|
59
|
+
return CommandNodeFactory.create(name, command_type, aliases, default, children)
|
|
60
|
+
|
|
61
|
+
return [parse_command(name, command_dict) for name, command_dict in commands.items()]
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def parse_states(states_dict: Dict[str, List[str]]) -> List[StateNode]:
|
|
65
|
+
"""Parses states in config.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
states_dict: The states dictionary.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The list of the state nodes.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
return [
|
|
75
|
+
StateNode(state, state in get_args(InternalCommandStates), aliases)
|
|
76
|
+
for state, aliases in states_dict.items()
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def parse_config(config_dict: ConfigType) -> CommandsConfig:
|
|
81
|
+
"""Parses a validated config.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
config_dict: The config dictionary.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The parsed config.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
return CommandsConfig(
|
|
91
|
+
config_dict["version"],
|
|
92
|
+
Parser.parse_states(config_dict["states"]),
|
|
93
|
+
Parser.parse_commands(config_dict["commands"])
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def parse_input(settings: Settings, tokens: List[FinalInputProtocol]) -> InputDict:
|
|
98
|
+
"""Parses a user input to input dictionary.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
tokens: Final tokenized tokens.
|
|
102
|
+
debug: Determines debug messages display.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The parsed input dictionary.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
settings.logger.debug(f"Parser.parse_input(tokens={tokens}).")
|
|
109
|
+
|
|
110
|
+
path: List[str] = []
|
|
111
|
+
flags: Dict[str, Optional[bool]] = {}
|
|
112
|
+
options: Dict[str, Any] = {}
|
|
113
|
+
|
|
114
|
+
i: int = 0
|
|
115
|
+
while i < len(tokens):
|
|
116
|
+
settings.logger.debug(f"Iteration: {i}.")
|
|
117
|
+
|
|
118
|
+
token: FinalInputProtocol = tokens[i]
|
|
119
|
+
|
|
120
|
+
if token.type == FinalTokenType.WORD and isinstance(token, FinalInputTokenWord):
|
|
121
|
+
settings.logger.debug("Process `Word` token.")
|
|
122
|
+
path.append(token.value)
|
|
123
|
+
|
|
124
|
+
elif token.type == FinalTokenType.FLAG and isinstance(token, FinalInputTokenFlag):
|
|
125
|
+
settings.logger.debug("Process `Flag` token.")
|
|
126
|
+
flags[token.name] = token.value
|
|
127
|
+
|
|
128
|
+
elif token.type == FinalTokenType.OPTION and isinstance(token, FinalInputTokenOption):
|
|
129
|
+
settings.logger.debug("Process `Option` token.")
|
|
130
|
+
options[token.name] = token.value
|
|
131
|
+
|
|
132
|
+
i += 1
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"path": path,
|
|
136
|
+
"flags": flags,
|
|
137
|
+
"options": options
|
|
138
|
+
}
|
untils/processor.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""processor.py - `Processor` class for universal pipe-lines."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from untils.utils.type_aliases import UnknownConfigType, ConfigType, InputDict
|
|
6
|
+
from untils.utils.protocols import FinalInputProtocol
|
|
7
|
+
|
|
8
|
+
from untils.ioreader import IOReader
|
|
9
|
+
from untils.config_validator import ConfigValidator
|
|
10
|
+
from untils.parser import Parser
|
|
11
|
+
from untils.commands_config import CommandsConfig
|
|
12
|
+
from untils.settings import Settings
|
|
13
|
+
from untils.input_token import RawInputToken
|
|
14
|
+
from untils.tokenizer import Tokenizer
|
|
15
|
+
from untils.input_validator import InputValidator
|
|
16
|
+
|
|
17
|
+
class Processor:
|
|
18
|
+
"""Processor class for config processing."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def load_config(settings: Settings, file_path: str) -> CommandsConfig:
|
|
22
|
+
"""Loads config.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
settings: The settings.
|
|
26
|
+
file_path: The file path.
|
|
27
|
+
debug: Determines debug messages display.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Validated and parsed config.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
settings.logger.debug(f"Load config by path: '{file_path}'.")
|
|
34
|
+
|
|
35
|
+
### 1. IOReader ###
|
|
36
|
+
settings.logger.debug("Reading the file.")
|
|
37
|
+
content: UnknownConfigType = IOReader.read_file(settings, file_path)
|
|
38
|
+
settings.logger.debug(f"Content: {content}.")
|
|
39
|
+
|
|
40
|
+
### 2. ConfigValidator ###
|
|
41
|
+
settings.logger.debug("Validating the config.")
|
|
42
|
+
raw_config: ConfigType = ConfigValidator.validate_config(settings, content)
|
|
43
|
+
settings.logger.debug(f"Intermediate config: {raw_config}.")
|
|
44
|
+
|
|
45
|
+
### 3. Parser ###
|
|
46
|
+
settings.logger.debug("Parsing.")
|
|
47
|
+
config: CommandsConfig = Parser.parse_config(raw_config)
|
|
48
|
+
settings.logger.debug(f"Parsed config: {config}.")
|
|
49
|
+
|
|
50
|
+
return config
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def process_input(
|
|
54
|
+
settings: Settings,
|
|
55
|
+
config: Optional[CommandsConfig],
|
|
56
|
+
input_str: str
|
|
57
|
+
) -> InputDict:
|
|
58
|
+
"""Validates a user input.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
settings: The settings.
|
|
62
|
+
input_str: The user input.
|
|
63
|
+
debug: Determines debug messages display.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Validated and parsed input dict.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
settings.logger.debug(f"Processing input string: '{input_str}'.")
|
|
70
|
+
|
|
71
|
+
### 1. Tokenizer ###
|
|
72
|
+
settings.logger.debug("Tokenizing the input.")
|
|
73
|
+
tokenizer: Tokenizer = Tokenizer(settings, input_str)
|
|
74
|
+
tokens: List[RawInputToken] = tokenizer.tokenize_input()
|
|
75
|
+
settings.logger.debug(f"Tokens: {tokens}.")
|
|
76
|
+
|
|
77
|
+
### 2. InputValidator ###
|
|
78
|
+
settings.logger.debug("Validating the input.")
|
|
79
|
+
input_validator: InputValidator = InputValidator(settings, config, tokens)
|
|
80
|
+
validated_tokens: List[FinalInputProtocol] = input_validator.validate_input(settings)
|
|
81
|
+
settings.logger.debug(f"Validated tokens: {validated_tokens}.")
|
|
82
|
+
|
|
83
|
+
### 3. Parser ###
|
|
84
|
+
settings.logger.debug("Parsing the input.")
|
|
85
|
+
parsed_representation: InputDict = Parser.parse_input(settings, validated_tokens)
|
|
86
|
+
settings.logger.debug(f"Parsed input: {parsed_representation}.")
|
|
87
|
+
|
|
88
|
+
return parsed_representation
|
untils/settings.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""settings.py - Global context settings for much part of all library."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnnecessaryIsInstance=false
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple, Type, Union
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from untils.utils.enums import WarningsLevel, InternalState
|
|
11
|
+
from untils.utils.decorators import alternative
|
|
12
|
+
from untils.utils.constants import Strings
|
|
13
|
+
from untils.utils.lib_warnings import ConfigError, ConfigWarning
|
|
14
|
+
|
|
15
|
+
class Settings:
|
|
16
|
+
"""The global context settings."""
|
|
17
|
+
|
|
18
|
+
__slots__ = ["__warnings_level", "__current_state", "__logger"]
|
|
19
|
+
|
|
20
|
+
__warnings_level: WarningsLevel
|
|
21
|
+
__current_state: str
|
|
22
|
+
__logger: logging.Logger
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def warnings_level(self) -> WarningsLevel:
|
|
26
|
+
"""Warnings level, which defines strictness of code flow."""
|
|
27
|
+
return self.__warnings_level
|
|
28
|
+
|
|
29
|
+
@warnings_level.setter
|
|
30
|
+
def warnings_level(self, value: WarningsLevel) -> None:
|
|
31
|
+
self.__warnings_level = value
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def current_state(self) -> str:
|
|
35
|
+
"""Current commands state."""
|
|
36
|
+
return self.__current_state
|
|
37
|
+
|
|
38
|
+
@current_state.setter
|
|
39
|
+
def current_state(self, value: Union[str, InternalState]) -> None:
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
if value != InternalState.INIT.value:
|
|
42
|
+
self.warning(
|
|
43
|
+
Strings.INVALID_INTERNAL_STATE_CHANGE.substitute(state=value),
|
|
44
|
+
Strings.AUTO_CORRECT_TO_LATEST,
|
|
45
|
+
RuntimeWarning,
|
|
46
|
+
ValueError
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
self.__current_state = value
|
|
50
|
+
elif isinstance(value, InternalState):
|
|
51
|
+
if value != InternalState.INIT:
|
|
52
|
+
self.warning(
|
|
53
|
+
Strings.INVALID_INTERNAL_STATE_CHANGE.substitute(state=value),
|
|
54
|
+
Strings.AUTO_CORRECT_TO_LATEST,
|
|
55
|
+
RuntimeWarning,
|
|
56
|
+
ValueError
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
self.__current_state = value.value
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def logger(self) -> logging.Logger:
|
|
63
|
+
"""Returns settings logger."""
|
|
64
|
+
return self.__logger
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
self.__warnings_level = WarningsLevel.STRICT
|
|
68
|
+
self.__current_state = InternalState.INIT.value
|
|
69
|
+
self.__logger = logging.getLogger(__name__)
|
|
70
|
+
self.__logger.debug(Strings.LOG_SETTINGS_INIT)
|
|
71
|
+
|
|
72
|
+
@alternative(version=Strings.ANY_VERSION)
|
|
73
|
+
def get_warnings_level(self) -> WarningsLevel:
|
|
74
|
+
"""[!ALT] Get a warnings level.\n
|
|
75
|
+
<This method is alternative for the standart. In real product use the property `Settings.warnings_level`.>
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The current warnings level.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return self.__warnings_level
|
|
82
|
+
|
|
83
|
+
@alternative(version=Strings.ANY_VERSION)
|
|
84
|
+
def set_warnings_level(self, warnings_level: WarningsLevel=WarningsLevel.STRICT) -> None:
|
|
85
|
+
"""[!ALT] Sets a warnings level.\n
|
|
86
|
+
<This method is alternative for the standart. In real product use the property `Settings.warnings_level`.>
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
warnings_level: The warnings level.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
self.__warnings_level = warnings_level
|
|
93
|
+
|
|
94
|
+
def warning(
|
|
95
|
+
self,
|
|
96
|
+
message: str,
|
|
97
|
+
auto_correct: str,
|
|
98
|
+
warning_type: Type[Warning]=ConfigWarning,
|
|
99
|
+
exception_type: Type[Exception]=ConfigError,
|
|
100
|
+
warning_levels: Optional[Union[Tuple[WarningsLevel, ...], Tuple[None]]]=None,
|
|
101
|
+
exception_levels: Optional[Union[Tuple[WarningsLevel, ...], Tuple[None]]]=None
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Warning or exception in validators."""
|
|
104
|
+
|
|
105
|
+
if self.warnings_level in (warning_levels or (WarningsLevel.BASIC,)):
|
|
106
|
+
self.logger.warning(message + ' ' + auto_correct, stacklevel=3)
|
|
107
|
+
warnings.warn(
|
|
108
|
+
message + ' ' + auto_correct,
|
|
109
|
+
warning_type,
|
|
110
|
+
stacklevel=3
|
|
111
|
+
)
|
|
112
|
+
elif self.warnings_level in (exception_levels or (WarningsLevel.STRICT,)):
|
|
113
|
+
self.logger.error(message, stacklevel=3)
|
|
114
|
+
raise exception_type(message)
|
untils/tokenizer.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""tokenizer.py - Tokenize raw user input string to raw tokens."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Literal, cast
|
|
4
|
+
|
|
5
|
+
from untils.utils.enums import RawTokenType
|
|
6
|
+
from untils.input_token import RawInputToken
|
|
7
|
+
|
|
8
|
+
from untils.settings import Settings
|
|
9
|
+
|
|
10
|
+
class Tokenizer:
|
|
11
|
+
"""Tokenizer class, which tokenizes user input."""
|
|
12
|
+
|
|
13
|
+
__slots__ = ["_settings", "_input_str", "_result", "_i"]
|
|
14
|
+
|
|
15
|
+
_settings: Settings
|
|
16
|
+
"""The settings."""
|
|
17
|
+
_input_str: str
|
|
18
|
+
"""The user input string."""
|
|
19
|
+
_result: List[RawInputToken]
|
|
20
|
+
"""The processed raw tokens."""
|
|
21
|
+
_i: int
|
|
22
|
+
"""Tokenize index."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, settings: Settings, input_str: str) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Args:
|
|
27
|
+
input_str: The user input.
|
|
28
|
+
debug: Determines debug messages display.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
self._settings = settings
|
|
32
|
+
self._input_str = input_str
|
|
33
|
+
self._result = []
|
|
34
|
+
self._i = 0
|
|
35
|
+
|
|
36
|
+
def tokenize_string(self) -> None:
|
|
37
|
+
"""Tokenizes the `String` type."""
|
|
38
|
+
|
|
39
|
+
string_char: Literal['\'', '\"'] = cast(Literal['\'', '\"'], self._input_str[self._i])
|
|
40
|
+
self._i += 1
|
|
41
|
+
string: str = ""
|
|
42
|
+
|
|
43
|
+
while self._i < len(self._input_str) and self._input_str[self._i] != string_char:
|
|
44
|
+
if self._input_str[self._i] == '\\':
|
|
45
|
+
if self._input_str[self._i + 1] in ('\'', '\"'):
|
|
46
|
+
string += self._input_str[self._i + 1]
|
|
47
|
+
elif self._input_str[self._i + 1] == '\\':
|
|
48
|
+
string += self._input_str[self._i]
|
|
49
|
+
|
|
50
|
+
self._i += 1
|
|
51
|
+
else:
|
|
52
|
+
string += self._input_str[self._i]
|
|
53
|
+
|
|
54
|
+
self._i += 1
|
|
55
|
+
|
|
56
|
+
self._result.append(RawInputToken(RawTokenType.STRING, string))
|
|
57
|
+
self._settings.logger.debug(f"String: '{string}'")
|
|
58
|
+
|
|
59
|
+
def tokenize_word(self) -> None:
|
|
60
|
+
"""Tokenizes the `Word` type."""
|
|
61
|
+
|
|
62
|
+
start: int = self._i
|
|
63
|
+
|
|
64
|
+
while self._i < len(self._input_str) and self._input_str[self._i].isalnum():
|
|
65
|
+
self._i += 1
|
|
66
|
+
|
|
67
|
+
word: str = self._input_str[start:self._i]
|
|
68
|
+
self._result.append(RawInputToken(RawTokenType.WORD, word))
|
|
69
|
+
self._settings.logger.debug(f"Word: '{word}'")
|
|
70
|
+
|
|
71
|
+
def tokenize_input(self) -> List[RawInputToken]:
|
|
72
|
+
"""Tokenizes the input.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Unvalidated raw tokens.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self._settings.logger.debug(f"Tokenizer.tokenize_input(input_str='{self._input_str}')")
|
|
79
|
+
|
|
80
|
+
self._result = []
|
|
81
|
+
self._i = 0
|
|
82
|
+
while self._i < len(self._input_str):
|
|
83
|
+
self._settings.logger.debug(f"Current character: {self._input_str[self._i]}.")
|
|
84
|
+
|
|
85
|
+
if self._input_str[self._i] == ' ':
|
|
86
|
+
self._settings.logger.debug("Process Space character.")
|
|
87
|
+
self._result.append(RawInputToken(RawTokenType.SPACE, ' '))
|
|
88
|
+
|
|
89
|
+
elif self._input_str[self._i] == '-':
|
|
90
|
+
self._settings.logger.debug("Process Minus character.")
|
|
91
|
+
self._result.append(RawInputToken(RawTokenType.MINUS, '-'))
|
|
92
|
+
|
|
93
|
+
if self._input_str[self._i] == '!':
|
|
94
|
+
self._settings.logger.debug("Process Not character.")
|
|
95
|
+
self._result.append(RawInputToken(RawTokenType.NOT, '!'))
|
|
96
|
+
|
|
97
|
+
elif self._input_str[self._i] in ('\'', '\"'):
|
|
98
|
+
self._settings.logger.debug("Process `String` construction.")
|
|
99
|
+
self.tokenize_string()
|
|
100
|
+
|
|
101
|
+
elif self._input_str[self._i].isalnum():
|
|
102
|
+
self._settings.logger.debug("Process `Word` construction.")
|
|
103
|
+
self.tokenize_word()
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
self._i += 1
|
|
107
|
+
|
|
108
|
+
return self._result
|
untils/utils/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""This module represents all tools, which need for main module."""
|
|
2
|
+
|
|
3
|
+
# pyright: reportUnusedImport=false
|
|
4
|
+
# ^^^^^^^ (Public imports.)
|
|
5
|
+
|
|
6
|
+
from untils.utils.lib_warnings import (
|
|
7
|
+
ConfigWarning, FileWarning, ConfigStructureWarning, ConfigValuesWarning, InputWarning,
|
|
8
|
+
InputStructureWarning,InputValuesWarning, ConfigError, FileError, ConfigStructureError,
|
|
9
|
+
ConfigValuesError, InputError, InputStructureError, InputValuesError
|
|
10
|
+
)
|
|
11
|
+
from untils.utils.type_aliases import (
|
|
12
|
+
ConfigVersion, CommandClass, UnknownCommandClass, CommandType,
|
|
13
|
+
InternalCommandStates, CommandStates, ConfigSupportedExtensions, UnknownCommandConfig,
|
|
14
|
+
WordCommandConfig, FallbackCommandConfig, FlagCommandConfig, OptionCommandConfig,
|
|
15
|
+
ConfigType, UnknownConfigType, InputDict, CommandPath
|
|
16
|
+
)
|
|
17
|
+
from untils.utils.constants import Constants, Strings
|
|
18
|
+
from untils.utils.decorators import deprecated, alternative
|
|
19
|
+
from untils.utils.enums import (
|
|
20
|
+
WarningsLevel, ConfigVersions, RawTokenType, FinalTokenType, InternalState
|
|
21
|
+
)
|
|
22
|
+
from untils.utils.protocols import IOReaderMixin, Factoric, FinalInputProtocol
|