socx-cli 0.1.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.
- socx_cli-0.1.0.dist-info/METADATA +34 -0
- socx_cli-0.1.0.dist-info/RECORD +41 -0
- socx_cli-0.1.0.dist-info/WHEEL +4 -0
- socx_cli-0.1.0.dist-info/entry_points.txt +2 -0
- socx_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- src/plugins/config.py +92 -0
- src/plugins/convert/lst.py +12 -0
- src/plugins/example.py +43 -0
- src/plugins/rgr.py +28 -0
- src/socx/__init__.py +132 -0
- src/socx/__main__.py +5 -0
- src/socx/cli.py +169 -0
- src/socx/config/__init__.py +88 -0
- src/socx/config/_config.py +357 -0
- src/socx/console.py +8 -0
- src/socx/converter.py +71 -0
- src/socx/core/__init__.py +2 -0
- src/socx/core/_uid.py +36 -0
- src/socx/core/mixins.py +34 -0
- src/socx/formatter.py +61 -0
- src/socx/log.py +224 -0
- src/socx/memory.py +86 -0
- src/socx/parser.py +153 -0
- src/socx/reader.py +47 -0
- src/socx/regression/__init__.py +14 -0
- src/socx/regression/regression.py +129 -0
- src/socx/regression/test.py +244 -0
- src/socx/static/toml/convert.local.toml +2 -0
- src/socx/static/toml/converter.toml +119 -0
- src/socx/static/toml/filetypes.toml +104 -0
- src/socx/static/toml/plugins.toml +5 -0
- src/socx/static/toml/regression.toml +47 -0
- src/socx/static/toml/settings.toml +6 -0
- src/socx/static/toml/test.toml +9 -0
- src/socx/tokenizer.py +63 -0
- src/socx/validators.py +57 -0
- src/socx/visitor.py +70 -0
- src/socx/writer.py +58 -0
- src/socx-tui/__init__.py +0 -0
- src/socx-tui/app.py +7 -0
- src/socx-tui/regression/table.py +0 -0
src/socx/cli.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from types import CodeType
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from trogon import tui
|
|
7
|
+
|
|
8
|
+
from .log import logger
|
|
9
|
+
from .console import console
|
|
10
|
+
from .config import settings
|
|
11
|
+
from .config import USER_CONFIG_DIR
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_CONTEXT_SETTINGS = dict(
|
|
15
|
+
help_option_names=[
|
|
16
|
+
"?",
|
|
17
|
+
"-h",
|
|
18
|
+
"--help",
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RichHelp:
|
|
24
|
+
def get_help(self, ctx: click.Context) -> None:
|
|
25
|
+
return self._header(ctx) + super().get_help(ctx) + self._footer()
|
|
26
|
+
|
|
27
|
+
def _header(self, ctx: click.Context) -> str:
|
|
28
|
+
path_text = "->".join(ctx.command_path.split())
|
|
29
|
+
with console.capture() as header:
|
|
30
|
+
console.line(1)
|
|
31
|
+
console.rule("")
|
|
32
|
+
console.print("[b]Help & Usage", justify="center")
|
|
33
|
+
console.print(f"[b][u]({path_text})", justify="center")
|
|
34
|
+
return header.get()
|
|
35
|
+
|
|
36
|
+
def _footer(self) -> str:
|
|
37
|
+
with console.capture() as footer:
|
|
38
|
+
console.line(1)
|
|
39
|
+
console.rule("")
|
|
40
|
+
return footer.get()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RichCommand(RichHelp, click.Command):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RichGroup(RichHelp, click.Group):
|
|
48
|
+
def group(self, *args, **kwargs) -> click.RichGroup:
|
|
49
|
+
kwargs["cls"] = RichGroup
|
|
50
|
+
return super().group(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def command(self, *args, **kwargs) -> click.RichCommand:
|
|
53
|
+
kwargs["cls"] = RichCommand
|
|
54
|
+
return super().command(*args, **kwargs)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CmdLine(RichGroup, click.Group):
|
|
58
|
+
def __init__(self, *args, **kwargs):
|
|
59
|
+
kwargs["context_settings"] = _CONTEXT_SETTINGS
|
|
60
|
+
super().__init__(*args, **kwargs)
|
|
61
|
+
self._plugins = None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def plugins(self) -> list[click.Command]:
|
|
65
|
+
if self._plugins is None:
|
|
66
|
+
self._load_plugins()
|
|
67
|
+
return self._plugins
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def plugin_names(self) -> list[str]:
|
|
71
|
+
return [cmd.name for cmd in self.plugins.values()]
|
|
72
|
+
|
|
73
|
+
def get_command(self, ctx: click.Context, name: str) -> CodeType:
|
|
74
|
+
logger.debug(f"get_command({ctx=}, {name=}) was called...")
|
|
75
|
+
if name in self.plugins and self.plugins[name] is not None:
|
|
76
|
+
rv = self.plugins[name]
|
|
77
|
+
logger.debug(
|
|
78
|
+
f"get_command({ctx=}, {name=}) returning plugin {rv=}."
|
|
79
|
+
)
|
|
80
|
+
return rv
|
|
81
|
+
rv = super().get_command(ctx, name)
|
|
82
|
+
logger.debug(f"get_command({ctx=}, {name=}) returning command {rv=}.")
|
|
83
|
+
return rv
|
|
84
|
+
|
|
85
|
+
def list_commands(self, ctx) -> list[str]:
|
|
86
|
+
logger.debug(f"list_commands({ctx=}) was called...")
|
|
87
|
+
rv = list(self.plugins.values())
|
|
88
|
+
rv += super().list_commands(ctx)
|
|
89
|
+
logger.debug(f"list_commands({ctx=}) returning {rv=}.")
|
|
90
|
+
return rv
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def _listify(cls, args: str | list | tuple | set | dict) -> list:
|
|
94
|
+
logger.debug(f"{cls.__name__}._listify called with {args=}")
|
|
95
|
+
if isinstance(args, list):
|
|
96
|
+
rv = args
|
|
97
|
+
elif isinstance(args, dict):
|
|
98
|
+
rv = list(args.values())
|
|
99
|
+
elif isinstance(args, set | tuple):
|
|
100
|
+
rv = list(args)
|
|
101
|
+
else:
|
|
102
|
+
rv = [args]
|
|
103
|
+
logger.debug(f"{cls.__name__}._listify returning value '{rv=}'")
|
|
104
|
+
return rv
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def _unique(cls, args: str | list | tuple | set) -> list:
|
|
108
|
+
logger.debug(f"{cls.__name__}._unique called with {args=}")
|
|
109
|
+
lookup = set()
|
|
110
|
+
args = cls._listify(args)
|
|
111
|
+
args = [
|
|
112
|
+
x for x in args if args not in lookup and lookup.add(x) is None
|
|
113
|
+
]
|
|
114
|
+
logger.debug(f"{cls.__name__}._unique returning {args=}")
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def _compile(cls, file, name):
|
|
118
|
+
logger.debug(f"{cls.__name__}._compile called with {file=}, {name=}")
|
|
119
|
+
ns = {}
|
|
120
|
+
logger.debug(f"compiling '{name}' (plugin: {file})...")
|
|
121
|
+
code = compile(file.read_text(), name, "exec")
|
|
122
|
+
logger.debug(f"'{name}' compiled.")
|
|
123
|
+
eval(code, ns, ns)
|
|
124
|
+
logger.debug(f"'{name}' evaluated.")
|
|
125
|
+
plugin = ns.get("cli")
|
|
126
|
+
if plugin:
|
|
127
|
+
logger.debug(f"'{name}' loaded.")
|
|
128
|
+
logger.debug(f"{cls.__name__}._compile returning {plugin=}")
|
|
129
|
+
return plugin
|
|
130
|
+
cls._missing_cli_err(name)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def _load_plugins(self) -> None:
|
|
134
|
+
if self._plugins is None:
|
|
135
|
+
self._plugins = {}
|
|
136
|
+
else:
|
|
137
|
+
self._plugins.clear()
|
|
138
|
+
for name, path in settings.plugins.items():
|
|
139
|
+
cmd = self._compile(path, name)
|
|
140
|
+
self._plugins[name] = cmd
|
|
141
|
+
for name, cmd in self._plugins.items():
|
|
142
|
+
self.add_command(cmd, name)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def _missing_cli_err(cls, name) -> None:
|
|
146
|
+
err = (
|
|
147
|
+
f"failed to load '{name}' (plugin).\n"
|
|
148
|
+
"please make ensure that function 'cli' is defined and has "
|
|
149
|
+
"either @group or @command decorator applied."
|
|
150
|
+
)
|
|
151
|
+
exc = ValueError(err)
|
|
152
|
+
logger.exception(err, exc_info=exc)
|
|
153
|
+
logger.debug(f"'{name}' (plugin) unloaded", exc_info=exc)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@tui()
|
|
157
|
+
@click.group("socx", cls=CmdLine)
|
|
158
|
+
@click.option(
|
|
159
|
+
"--configure/--no-configure",
|
|
160
|
+
default=True,
|
|
161
|
+
show_default=True,
|
|
162
|
+
help="load/dont-load user configurations at startup.",
|
|
163
|
+
)
|
|
164
|
+
def cli(configure: bool) -> None:
|
|
165
|
+
"""SoC team tool executer and plugin manager."""
|
|
166
|
+
if configure:
|
|
167
|
+
from .config._config import _load_settings
|
|
168
|
+
|
|
169
|
+
_load_settings(USER_CONFIG_DIR)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Management of application configuration settings.
|
|
3
|
+
|
|
4
|
+
Configurations are defined in .toml files located inside the 'config'
|
|
5
|
+
directory.
|
|
6
|
+
|
|
7
|
+
The default configurations are under settings.toml and can be used as a
|
|
8
|
+
reference.
|
|
9
|
+
|
|
10
|
+
Local configurations may be defined in 'settings.local.toml'.
|
|
11
|
+
|
|
12
|
+
Any local configration have priority over the defaults and will either
|
|
13
|
+
override the default, or be merged with it if dynaconf_merge is true,
|
|
14
|
+
and the keys do not conflict.
|
|
15
|
+
|
|
16
|
+
Reference the settings.toml file for an example of how configurations are
|
|
17
|
+
defined.
|
|
18
|
+
|
|
19
|
+
For additional information regarding the internals of this module, reference
|
|
20
|
+
dynaconf documentation on its official-site/github-repository.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from rich.tree import Tree
|
|
28
|
+
from rich.table import Table
|
|
29
|
+
from dynaconf import Dynaconf
|
|
30
|
+
from dynaconf.utils.boxing import DynaBox
|
|
31
|
+
|
|
32
|
+
from ._config import _to_tree
|
|
33
|
+
from ._config import _get_settings
|
|
34
|
+
|
|
35
|
+
__all__ = (
|
|
36
|
+
# API
|
|
37
|
+
"settings",
|
|
38
|
+
"settings_tree",
|
|
39
|
+
# Metadata
|
|
40
|
+
"APP_NAME",
|
|
41
|
+
"APP_AUTHOR",
|
|
42
|
+
"APP_VERSION",
|
|
43
|
+
# Directories
|
|
44
|
+
"USER_LOG_DIR",
|
|
45
|
+
"USER_DATA_DIR",
|
|
46
|
+
"USER_CACHE_DIR",
|
|
47
|
+
"USER_STATE_DIR",
|
|
48
|
+
"USER_CONFIG_DIR",
|
|
49
|
+
"USER_RUNTIME_DIR",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
from ._config import APP_NAME as APP_NAME
|
|
54
|
+
from ._config import APP_AUTHOR as APP_AUTHOR
|
|
55
|
+
from ._config import APP_VERSION as APP_VERSION
|
|
56
|
+
from ._config import USER_LOG_DIR as USER_LOG_DIR
|
|
57
|
+
from ._config import USER_DATA_DIR as USER_DATA_DIR
|
|
58
|
+
from ._config import USER_CACHE_DIR as USER_CACHE_DIR
|
|
59
|
+
from ._config import USER_STATE_DIR as USER_STATE_DIR
|
|
60
|
+
from ._config import USER_CONFIG_DIR as USER_CONFIG_DIR
|
|
61
|
+
from ._config import USER_RUNTIME_DIR as USER_RUNTIME_DIR
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
settings = _get_settings()
|
|
65
|
+
"""
|
|
66
|
+
Global settings instance.
|
|
67
|
+
|
|
68
|
+
Any attribute/key accesses to this instance trigger a lazy loading operation
|
|
69
|
+
which will attempt to find and read the value of the attribute from any of the
|
|
70
|
+
.toml configuration files under the 'settings' directory.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def reconfigure(path: Path) -> Dynaconf:
|
|
75
|
+
from ._config import _load_settings
|
|
76
|
+
_load_settings(path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def settings_tree(
|
|
80
|
+
root: Dynaconf | DynaBox | dict,
|
|
81
|
+
label: str = "Settings",
|
|
82
|
+
) -> Tree | Table:
|
|
83
|
+
"""Get a tree representation of a dynaconf settings instance."""
|
|
84
|
+
if isinstance(root, Dynaconf):
|
|
85
|
+
root = root.as_dict()
|
|
86
|
+
if not isinstance(root, dict | list | tuple | set):
|
|
87
|
+
root = str(root)
|
|
88
|
+
return _to_tree(label, root)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
from typing import Final
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
from importlib.metadata import metadata
|
|
9
|
+
from importlib.metadata import PackageMetadata
|
|
10
|
+
from collections.abc import Iterable
|
|
11
|
+
|
|
12
|
+
from click import open_file
|
|
13
|
+
from rich.tree import Tree
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.console import Group
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.logging import RichHandler
|
|
18
|
+
from dynaconf import Dynaconf
|
|
19
|
+
from dynaconf import ValidationError
|
|
20
|
+
from dynaconf import add_converter
|
|
21
|
+
from dynaconf.validator import empty
|
|
22
|
+
from dynaconf.validator import Validator
|
|
23
|
+
from platformdirs import site_data_dir
|
|
24
|
+
from platformdirs import site_cache_dir
|
|
25
|
+
from platformdirs import site_config_dir
|
|
26
|
+
from platformdirs import site_runtime_dir
|
|
27
|
+
from platformdirs import user_log_dir
|
|
28
|
+
from platformdirs import user_data_dir
|
|
29
|
+
from platformdirs import user_state_dir
|
|
30
|
+
from platformdirs import user_cache_dir
|
|
31
|
+
from platformdirs import user_config_dir
|
|
32
|
+
from platformdirs import user_runtime_dir
|
|
33
|
+
|
|
34
|
+
from ..log import logger
|
|
35
|
+
from ..log import add_handler
|
|
36
|
+
from ..log import DEFAULT_LEVEL
|
|
37
|
+
from ..log import DEFAULT_TIME_FORMAT
|
|
38
|
+
from ..validators import PathValidator
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__author__ = "Sagi Kimhi <sagi.kim5@gmail.com>"
|
|
42
|
+
|
|
43
|
+
__version__ = version(__package__.partition(".")[0])
|
|
44
|
+
|
|
45
|
+
__metadata__ = metadata(__package__.partition(".")[0])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
PACKAGE_NAME: Final[str] = __package__.partition(".")[0]
|
|
49
|
+
"""Package name."""
|
|
50
|
+
|
|
51
|
+
PACKAGE_PATH: Final[Path] = Path(sys.modules[PACKAGE_NAME].__file__).parent
|
|
52
|
+
"""Absolute path to package."""
|
|
53
|
+
|
|
54
|
+
PACKAGE_AUTHOR: Final[str] = __author__
|
|
55
|
+
"""Package author."""
|
|
56
|
+
|
|
57
|
+
PACKAGE_VERSION: Final[str] = __version__
|
|
58
|
+
"""Package version."""
|
|
59
|
+
|
|
60
|
+
PACKAGE_METADATA: Final[PackageMetadata] = __metadata__
|
|
61
|
+
"""Package metadata."""
|
|
62
|
+
|
|
63
|
+
APP_NAME: Final[str] = PACKAGE_NAME
|
|
64
|
+
"""Application name."""
|
|
65
|
+
|
|
66
|
+
APP_AUTHOR: Final[str] = PACKAGE_METADATA
|
|
67
|
+
"""Application author"""
|
|
68
|
+
|
|
69
|
+
APP_VERSION: Final[str] = PACKAGE_VERSION
|
|
70
|
+
"""Application version."""
|
|
71
|
+
|
|
72
|
+
SITE_DATA_DIR: Final[Path] = Path(
|
|
73
|
+
site_data_dir(
|
|
74
|
+
appname=APP_NAME,
|
|
75
|
+
version=APP_VERSION,
|
|
76
|
+
appauthor=APP_AUTHOR,
|
|
77
|
+
ensure_exists=False,
|
|
78
|
+
)
|
|
79
|
+
).resolve()
|
|
80
|
+
"""Absolute path to platform's native application data directory."""
|
|
81
|
+
|
|
82
|
+
SITE_CACHE_DIR: Final[Path] = Path(
|
|
83
|
+
site_cache_dir(
|
|
84
|
+
appname=APP_NAME,
|
|
85
|
+
version=APP_VERSION,
|
|
86
|
+
appauthor=APP_AUTHOR,
|
|
87
|
+
ensure_exists=False,
|
|
88
|
+
)
|
|
89
|
+
).resolve()
|
|
90
|
+
"""Absolute path to platform's native application cache directory."""
|
|
91
|
+
|
|
92
|
+
SITE_CONFIG_DIR: Final[Path] = Path(
|
|
93
|
+
site_config_dir(
|
|
94
|
+
appname=APP_NAME,
|
|
95
|
+
version=APP_VERSION,
|
|
96
|
+
appauthor=APP_AUTHOR,
|
|
97
|
+
ensure_exists=False,
|
|
98
|
+
)
|
|
99
|
+
).resolve()
|
|
100
|
+
"""Absolute path to platform's native application config directory."""
|
|
101
|
+
|
|
102
|
+
SITE_RUNTIME_DIR: Final[Path] = Path(
|
|
103
|
+
site_runtime_dir(
|
|
104
|
+
appname=APP_NAME,
|
|
105
|
+
version=APP_VERSION,
|
|
106
|
+
appauthor=APP_AUTHOR,
|
|
107
|
+
ensure_exists=False,
|
|
108
|
+
)
|
|
109
|
+
).resolve()
|
|
110
|
+
"""Absolute path to platform's native application runtime directory."""
|
|
111
|
+
|
|
112
|
+
USER_LOG_DIR: Final[Path] = Path(
|
|
113
|
+
user_log_dir(
|
|
114
|
+
appname=APP_NAME,
|
|
115
|
+
version=APP_VERSION,
|
|
116
|
+
appauthor=APP_AUTHOR,
|
|
117
|
+
ensure_exists=True,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
"""Absolute path to platform's native application logs directory."""
|
|
121
|
+
|
|
122
|
+
USER_DATA_DIR: Final[Path] = Path(
|
|
123
|
+
user_data_dir(
|
|
124
|
+
appname=APP_NAME,
|
|
125
|
+
version=APP_VERSION,
|
|
126
|
+
appauthor=APP_AUTHOR,
|
|
127
|
+
ensure_exists=True,
|
|
128
|
+
)
|
|
129
|
+
).resolve()
|
|
130
|
+
"""Absolute path to platform's native application data directory."""
|
|
131
|
+
|
|
132
|
+
USER_CACHE_DIR: Final[Path] = Path(
|
|
133
|
+
user_cache_dir(
|
|
134
|
+
appname=APP_NAME,
|
|
135
|
+
version=APP_VERSION,
|
|
136
|
+
appauthor=APP_AUTHOR,
|
|
137
|
+
ensure_exists=True,
|
|
138
|
+
)
|
|
139
|
+
).resolve()
|
|
140
|
+
"""Absolute path to platform's native application cache directory."""
|
|
141
|
+
|
|
142
|
+
USER_STATE_DIR: Final[Path] = Path(
|
|
143
|
+
user_state_dir(
|
|
144
|
+
appname=APP_NAME,
|
|
145
|
+
version=APP_VERSION,
|
|
146
|
+
appauthor=APP_AUTHOR,
|
|
147
|
+
ensure_exists=True,
|
|
148
|
+
)
|
|
149
|
+
).resolve()
|
|
150
|
+
"""Absolute path to platform's native application state directory."""
|
|
151
|
+
|
|
152
|
+
USER_CONFIG_DIR: Final[Path] = Path(
|
|
153
|
+
user_config_dir(
|
|
154
|
+
appname=APP_NAME,
|
|
155
|
+
version=APP_VERSION,
|
|
156
|
+
appauthor=APP_AUTHOR,
|
|
157
|
+
ensure_exists=True,
|
|
158
|
+
)
|
|
159
|
+
).resolve()
|
|
160
|
+
"""Absolute path to platform's native application config directory."""
|
|
161
|
+
|
|
162
|
+
USER_RUNTIME_DIR: Final[Path] = Path(
|
|
163
|
+
user_runtime_dir(
|
|
164
|
+
appname=APP_NAME,
|
|
165
|
+
version=APP_VERSION,
|
|
166
|
+
appauthor=APP_AUTHOR,
|
|
167
|
+
ensure_exists=True,
|
|
168
|
+
)
|
|
169
|
+
).resolve()
|
|
170
|
+
"""Absolute path to platform's native application runtime directory."""
|
|
171
|
+
|
|
172
|
+
_init_done: bool = False
|
|
173
|
+
|
|
174
|
+
_settings: Dynaconf | dict = {
|
|
175
|
+
name: value for name, value in vars().items() if not name.startswith("_")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_user_log: Path = USER_LOG_DIR / "run.log"
|
|
179
|
+
|
|
180
|
+
_static_dir: Path = Path(PACKAGE_PATH) / "static" / "toml"
|
|
181
|
+
|
|
182
|
+
_static_file: Path = "settings.toml"
|
|
183
|
+
|
|
184
|
+
_user_settings: Path = Path(USER_CONFIG_DIR) / "settings.toml"
|
|
185
|
+
|
|
186
|
+
_static_settings: Path = Path(_static_dir) / _static_file
|
|
187
|
+
|
|
188
|
+
_settings_kwargs = dict(
|
|
189
|
+
encoding="utf-8",
|
|
190
|
+
lowercase_read=True,
|
|
191
|
+
envvar_prefix=APP_NAME.upper(),
|
|
192
|
+
load_dotenv=True,
|
|
193
|
+
environments=False,
|
|
194
|
+
dotenv_override=True,
|
|
195
|
+
sysenv_fallback=True,
|
|
196
|
+
validate_on_update="all",
|
|
197
|
+
validators=[Validator(r"convert.*", ne=empty)],
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _init_module() -> None:
|
|
202
|
+
global _init_done
|
|
203
|
+
logger.debug("initializing module.")
|
|
204
|
+
_init_logger()
|
|
205
|
+
_init_converters()
|
|
206
|
+
_load_settings()
|
|
207
|
+
_validate_settings()
|
|
208
|
+
_init_done = True
|
|
209
|
+
logger.debug("module initialized.")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _init_logger() -> None:
|
|
213
|
+
global logger
|
|
214
|
+
logger.debug("initializing logger.")
|
|
215
|
+
add_handler(
|
|
216
|
+
RichHandler(
|
|
217
|
+
level=DEFAULT_LEVEL,
|
|
218
|
+
console=Console(
|
|
219
|
+
file=open_file(
|
|
220
|
+
filename=str(_user_log),
|
|
221
|
+
mode="w",
|
|
222
|
+
encoding="utf-8",
|
|
223
|
+
lazy=True,
|
|
224
|
+
),
|
|
225
|
+
tab_size=4,
|
|
226
|
+
width=110,
|
|
227
|
+
),
|
|
228
|
+
markup=False,
|
|
229
|
+
show_time=True,
|
|
230
|
+
show_level=True,
|
|
231
|
+
rich_tracebacks=True,
|
|
232
|
+
locals_max_string=None,
|
|
233
|
+
locals_max_length=None,
|
|
234
|
+
tracebacks_theme="monokai",
|
|
235
|
+
omit_repeated_times=False,
|
|
236
|
+
tracebacks_word_wrap=False,
|
|
237
|
+
tracebacks_show_locals=True,
|
|
238
|
+
log_time_format=DEFAULT_TIME_FORMAT,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
logger.info("logging initialized.")
|
|
242
|
+
logger.info(f"logging at path: {_user_log}")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _init_converters() -> None:
|
|
246
|
+
logger.debug("initializing settings converters.")
|
|
247
|
+
add_converter("path", lambda x: Path(str(x)).resolve())
|
|
248
|
+
add_converter("glob", lambda x: next(Path().glob(x)).resolve())
|
|
249
|
+
logger.debug("settings converters initialized.")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _load_settings(
|
|
253
|
+
path: Path | None = None,
|
|
254
|
+
preload: Iterable[str] | None = None,
|
|
255
|
+
includes: Iterable[str] | None = None,
|
|
256
|
+
) -> Dynaconf:
|
|
257
|
+
global _settings
|
|
258
|
+
path = path if path else _static_settings
|
|
259
|
+
preload = preload if preload else []
|
|
260
|
+
includes = includes if includes else []
|
|
261
|
+
logger.debug(f"loading settings from {path}.")
|
|
262
|
+
settings = Dynaconf(
|
|
263
|
+
root_path=path.parent,
|
|
264
|
+
preload=preload,
|
|
265
|
+
settings_files=[str(path)],
|
|
266
|
+
includes=includes,
|
|
267
|
+
**_settings_kwargs,
|
|
268
|
+
**_settings.as_dict()
|
|
269
|
+
if isinstance(_settings, Dynaconf)
|
|
270
|
+
else _settings,
|
|
271
|
+
)
|
|
272
|
+
settings.update(_settings)
|
|
273
|
+
_settings = settings
|
|
274
|
+
logger.debug("settings loaded.")
|
|
275
|
+
return _settings
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _validate_settings() -> None:
|
|
279
|
+
global _settings
|
|
280
|
+
logger.debug("Validating settings.")
|
|
281
|
+
for lang in _settings.convert:
|
|
282
|
+
_settings.validators.register(_get_convert_validators(lang))
|
|
283
|
+
accumulative_errors = ""
|
|
284
|
+
try:
|
|
285
|
+
_settings.validators.validate_all()
|
|
286
|
+
except ValidationError as e:
|
|
287
|
+
accumulative_errors = e.details
|
|
288
|
+
logger.debug("Settings validation failed.")
|
|
289
|
+
else:
|
|
290
|
+
logger.debug("Settings validation passed.")
|
|
291
|
+
finally:
|
|
292
|
+
if accumulative_errors:
|
|
293
|
+
errors = ValidationError(accumulative_errors)
|
|
294
|
+
logger.exception(accumulative_errors, exc_info=errors)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _get_settings() -> Dynaconf:
|
|
298
|
+
if not _init_done:
|
|
299
|
+
_init_module()
|
|
300
|
+
return _settings
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _get_convert_validators(lang: str) -> list[Validator]:
|
|
304
|
+
(
|
|
305
|
+
Validator(
|
|
306
|
+
f"convert.{lang}.source",
|
|
307
|
+
condition=PathValidator.source_validator,
|
|
308
|
+
must_exist=True,
|
|
309
|
+
ne=empty,
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
(
|
|
313
|
+
Validator(
|
|
314
|
+
f"convert.{lang}.target",
|
|
315
|
+
condition=PathValidator.target_validator,
|
|
316
|
+
must_exist=True,
|
|
317
|
+
ne=empty,
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _to_tree(key: str, val: Any) -> Tree | Table:
|
|
323
|
+
if isinstance(val, list | set | tuple):
|
|
324
|
+
node = Tree(key)
|
|
325
|
+
for i, v in enumerate(val):
|
|
326
|
+
k = f"{key}[{i}]"
|
|
327
|
+
if isinstance(v, dict):
|
|
328
|
+
node.add(Group(k, _to_table("", v)))
|
|
329
|
+
else:
|
|
330
|
+
node.add(_to_table(k, v))
|
|
331
|
+
elif isinstance(val, dict):
|
|
332
|
+
node = Tree(key)
|
|
333
|
+
for k, v in val.items():
|
|
334
|
+
node.add(_to_tree(k, v))
|
|
335
|
+
else:
|
|
336
|
+
node = _to_table(key, str(val))
|
|
337
|
+
return node
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _to_table(key: str, val: Any) -> Table:
|
|
341
|
+
node = Table()
|
|
342
|
+
node.show_lines = True
|
|
343
|
+
node.show_header = True
|
|
344
|
+
node.show_footer = False
|
|
345
|
+
if isinstance(val, list | tuple | set):
|
|
346
|
+
node.add_column("index")
|
|
347
|
+
node.add_column(key)
|
|
348
|
+
for i, v in enumerate(val):
|
|
349
|
+
node.add_row(str(i), str(v))
|
|
350
|
+
elif isinstance(val, dict):
|
|
351
|
+
for k in val:
|
|
352
|
+
node.add_column(k)
|
|
353
|
+
node.add_row(*[str(v) for v in val.values()])
|
|
354
|
+
else:
|
|
355
|
+
node.add_column(str(key))
|
|
356
|
+
node.add_row(str(val))
|
|
357
|
+
return node
|
src/socx/console.py
ADDED
src/socx/converter.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
from typing import override
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from dynaconf.utils.boxing import DynaBox
|
|
8
|
+
|
|
9
|
+
from .log import logger
|
|
10
|
+
from .config import settings
|
|
11
|
+
from .reader import Reader
|
|
12
|
+
from .reader import FileReader
|
|
13
|
+
from .writer import Writer
|
|
14
|
+
from .writer import FileWriter
|
|
15
|
+
from .parser import Parser
|
|
16
|
+
from .parser import LstParser
|
|
17
|
+
from .tokenizer import Tokenizer
|
|
18
|
+
from .tokenizer import LstTokenizer
|
|
19
|
+
from .formatter import Formatter
|
|
20
|
+
from .formatter import SystemVerilogFormatter
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(unsafe_hash=True)
|
|
24
|
+
class Converter(abc.ABC):
|
|
25
|
+
reader: Reader | None = None
|
|
26
|
+
writer: Writer | None = None
|
|
27
|
+
parser: Parser | None = None
|
|
28
|
+
tokenizer: Tokenizer | None = None
|
|
29
|
+
formatter: Formatter | None = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def cfg(self) -> DynaBox:
|
|
33
|
+
return settings.convert[self.lang]
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def lang(self) -> DynaBox: ...
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
def convert(self) -> None: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class LstConverter(Converter):
|
|
45
|
+
def __post_init__(self) -> None:
|
|
46
|
+
self.parser = LstParser()
|
|
47
|
+
self.reader = FileReader(
|
|
48
|
+
self.cfg.source, self.cfg.includes, self.cfg.excludes
|
|
49
|
+
)
|
|
50
|
+
self.writer = FileWriter(self.cfg.target)
|
|
51
|
+
self.tokenizer = LstTokenizer()
|
|
52
|
+
self.formatter = SystemVerilogFormatter()
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
@property
|
|
56
|
+
def lang(self) -> DynaBox:
|
|
57
|
+
return "lst"
|
|
58
|
+
|
|
59
|
+
def convert(self) -> None:
|
|
60
|
+
inputs = self.reader.read()
|
|
61
|
+
outputs = {path: "" for path in inputs}
|
|
62
|
+
self.parser.parse()
|
|
63
|
+
for path, input_text in inputs.items():
|
|
64
|
+
matches = self.tokenizer.tokenize(input_text)
|
|
65
|
+
outputs[path] = self.formatter.format(
|
|
66
|
+
self.tokenizer.token_map, matches, self.parser.sym_table
|
|
67
|
+
)
|
|
68
|
+
logger.debug(f"{input_text=}")
|
|
69
|
+
logger.debug(f"{outputs[path]=}")
|
|
70
|
+
logger.debug(f"{self.parser.sym_table=}")
|
|
71
|
+
self.writer.write(outputs[path], path.with_suffix(".svh").name)
|