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/tokenizer.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import abc
|
|
5
|
+
from typing import Self
|
|
6
|
+
from typing import override
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from dynaconf.utils.boxing import DynaBox
|
|
10
|
+
|
|
11
|
+
from .config import settings
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Tokenizer(abc.ABC):
|
|
15
|
+
"""Convert text to tokens."""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
self._matchs = {}
|
|
19
|
+
self._token_map = {token.name: token for token in self.tokens}
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def cfg(self) -> DynaBox:
|
|
23
|
+
return settings.lang.get(self.lang)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abc.abstractmethod
|
|
27
|
+
def lang(self) -> DynaBox: ...
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def tokens(self) -> DynaBox:
|
|
31
|
+
return self.cfg.tokens
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def token_map(self) -> dict[str, DynaBox]:
|
|
35
|
+
return self._token_map
|
|
36
|
+
|
|
37
|
+
@abc.abstractmethod
|
|
38
|
+
def tokenize(self: Self, text: str) -> tuple[re.Match]: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(unsafe_hash=True)
|
|
42
|
+
class LstTokenizer(Tokenizer):
|
|
43
|
+
|
|
44
|
+
def __init__(self) -> None:
|
|
45
|
+
super().__init__()
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
@property
|
|
49
|
+
def lang(self) -> DynaBox:
|
|
50
|
+
return "lst"
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def tokenize(self, text: str) -> tuple[re.Match]:
|
|
54
|
+
matches = []
|
|
55
|
+
flags = re.MULTILINE | re.DOTALL | re.VERBOSE
|
|
56
|
+
template = "|".join(
|
|
57
|
+
"(?P<%s>%s)" % (token.name, token.expr)
|
|
58
|
+
for token in self.tokens
|
|
59
|
+
)
|
|
60
|
+
pattern = re.compile(template, flags)
|
|
61
|
+
for line in text.splitlines():
|
|
62
|
+
matches.extend(match for match in pattern.finditer(line))
|
|
63
|
+
return tuple(matches)
|
src/socx/validators.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
from pathlib import Path as Path
|
|
3
|
+
|
|
4
|
+
__all__ = ("PathValidator",)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PathValidator:
|
|
8
|
+
src: t.ClassVar[Path] = None
|
|
9
|
+
target: t.ClassVar[Path] = None
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def source_validator(cls, src: str | Path) -> bool:
|
|
13
|
+
if not isinstance(src, Path):
|
|
14
|
+
src = Path(src)
|
|
15
|
+
return src.exists() and src.is_dir()
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def target_validator(cls, target: str | Path) -> bool:
|
|
19
|
+
if not isinstance(target, Path):
|
|
20
|
+
target = Path(target)
|
|
21
|
+
return target.is_dir() or not target.exists()
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def includes_validator(
|
|
25
|
+
cls,
|
|
26
|
+
src: Path,
|
|
27
|
+
includes: list[str] | set[str] | tuple[str, ...],
|
|
28
|
+
excludes: list[str] | set[str] | tuple[str, ...],
|
|
29
|
+
) -> bool:
|
|
30
|
+
if not includes:
|
|
31
|
+
return False
|
|
32
|
+
if not isinstance(src, Path):
|
|
33
|
+
src = Path(src)
|
|
34
|
+
if not isinstance(includes, list | set | tuple):
|
|
35
|
+
return False
|
|
36
|
+
paths = cls._extract_includes(src, includes, excludes)
|
|
37
|
+
return bool(paths) and all(path.is_file() for path in paths)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def _extract_includes(
|
|
41
|
+
cls, src: Path, includes: set[Path], excludes: set[Path]
|
|
42
|
+
) -> set[Path]:
|
|
43
|
+
paths = set()
|
|
44
|
+
globpaths = set()
|
|
45
|
+
if not isinstance(src, Path):
|
|
46
|
+
src = Path(src)
|
|
47
|
+
for include in includes:
|
|
48
|
+
if "*" not in include:
|
|
49
|
+
paths.add(Path(src / include))
|
|
50
|
+
else:
|
|
51
|
+
globpaths = globpaths.union(set(src.glob(str(include))))
|
|
52
|
+
for exclude in excludes:
|
|
53
|
+
if "*" not in exclude:
|
|
54
|
+
paths.discard(Path(src / exclude))
|
|
55
|
+
else:
|
|
56
|
+
globpaths.difference_update(set(src.glob(str(exclude))))
|
|
57
|
+
return paths.union(globpaths)
|
src/socx/visitor.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"Node",
|
|
8
|
+
"Visitor",
|
|
9
|
+
"Structure",
|
|
10
|
+
"Proxy",
|
|
11
|
+
"Adapter",
|
|
12
|
+
"TopDownTraversal",
|
|
13
|
+
"BottomUpTraversal",
|
|
14
|
+
"ByLevelTraversal",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Node[NODE](Protocol):
|
|
19
|
+
def accept(self, v: Visitor[NODE]) -> None:
|
|
20
|
+
"""Accept a visit from a visitor."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Visitor[NODE](Protocol):
|
|
25
|
+
def visit(self, n: NODE) -> None:
|
|
26
|
+
"""Visit a node."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Structure[NODE](Protocol):
|
|
31
|
+
def children(self) -> list[NODE]:
|
|
32
|
+
"""Retrieve the immediate child nodes of a structure."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Proxy[NODE](Protocol):
|
|
37
|
+
def children(self, n: NODE) -> list[NODE]:
|
|
38
|
+
"""Retrieve the immediate children of a node in a structure."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Adapter[NODE](Protocol):
|
|
43
|
+
def accept(self, n: NODE, v: Visitor[NODE], p: Proxy[NODE]) -> None:
|
|
44
|
+
"""Adapt a visitor to visit a structure of nodes through a proxy."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TopDownTraversal[NODE](Adapter):
|
|
49
|
+
def accept(self, n: NODE, v: Visitor[NODE], p: Proxy[NODE]) -> None:
|
|
50
|
+
n.accept(v)
|
|
51
|
+
for c in p.children(v):
|
|
52
|
+
self.accept(c, v, p)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BottomUpTraversal[NODE](Adapter):
|
|
56
|
+
def accept(self, n: NODE, v: Visitor[NODE], p: Proxy[NODE]) -> None:
|
|
57
|
+
for c in p.children(v):
|
|
58
|
+
self.accept(c, v, p)
|
|
59
|
+
v.visit(n)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ByLevelTraversal[NODE](Adapter[NODE]):
|
|
63
|
+
def accept(self, n: NODE, v: Visitor[NODE], p: Proxy[NODE]) -> None:
|
|
64
|
+
q = [n]
|
|
65
|
+
while q:
|
|
66
|
+
t = []
|
|
67
|
+
for n_ in q:
|
|
68
|
+
v.visit(n_)
|
|
69
|
+
t.extend(p.children(n_))
|
|
70
|
+
q = t
|
src/socx/writer.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import override
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from dataclasses import field
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .log import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Writer[T](abc.ABC):
|
|
12
|
+
"""Writes data to a target."""
|
|
13
|
+
|
|
14
|
+
@abc.abstractmethod
|
|
15
|
+
def write(self, data: T) -> None:
|
|
16
|
+
"""Write data to a target."""
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class FileWriter[T](Writer):
|
|
22
|
+
"""
|
|
23
|
+
Writes data to a file.
|
|
24
|
+
|
|
25
|
+
Members
|
|
26
|
+
-------
|
|
27
|
+
target: Path
|
|
28
|
+
Target to write to.
|
|
29
|
+
|
|
30
|
+
options: dict
|
|
31
|
+
Options for handling write requests.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
target: Path
|
|
35
|
+
options: dict[str, str] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def write(self, data: str, filename: str) -> None:
|
|
39
|
+
target = Path(self.target)/filename
|
|
40
|
+
if target.exists():
|
|
41
|
+
target.replace(target.with_suffix(".backup"))
|
|
42
|
+
target.write_text(data)
|
|
43
|
+
|
|
44
|
+
def __post_init__(self) -> None:
|
|
45
|
+
if not isinstance(self.target, Path):
|
|
46
|
+
self.target = Path(self.target)
|
|
47
|
+
if self.target.exists() and self.target.is_file():
|
|
48
|
+
self._invalid_target_err()
|
|
49
|
+
self.target.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
def _invalid_target_err(self) -> None:
|
|
52
|
+
err = (
|
|
53
|
+
f"Invalid target: '{self.target}'"
|
|
54
|
+
"Target path must point to a directory, not a file."
|
|
55
|
+
)
|
|
56
|
+
exc = ValueError(err)
|
|
57
|
+
logger.exception(err, exc_info=exc)
|
|
58
|
+
raise exc
|
src/socx-tui/__init__.py
ADDED
|
File without changes
|
src/socx-tui/app.py
ADDED
|
File without changes
|