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.
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
@@ -0,0 +1,8 @@
1
+ from rich import pretty
2
+ from rich.console import Console
3
+
4
+ __all__ = ("console",)
5
+
6
+ _console: Console = Console(record=True, tab_size=4, force_terminal=True)
7
+ pretty.install(_console, overflow="ignore", indent_guides=True, max_length=78)
8
+ console = _console
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)
@@ -0,0 +1,2 @@
1
+ from .mixins import TypedPtrMixin as TypedPtrMixin
2
+ from .mixins import UIDMixin as UIDMixin