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/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
File without changes
src/socx-tui/app.py ADDED
@@ -0,0 +1,7 @@
1
+ from textual.app import App, ComposeResult
2
+ from textual import on, work
3
+ from socx import settings
4
+
5
+
6
+ class SoCX(App):
7
+ pass
File without changes