diff options
Diffstat (limited to 'tools/cru-py')
| -rw-r--r-- | tools/cru-py/.gitignore | 1 | ||||
| -rw-r--r-- | tools/cru-py/.python-version | 2 | ||||
| -rw-r--r-- | tools/cru-py/cru/_iter.py | 3 | ||||
| -rw-r--r-- | tools/cru-py/cru/parsing.py | 192 | ||||
| -rw-r--r-- | tools/cru-py/cru/service/_nginx.py | 25 | ||||
| -rw-r--r-- | tools/cru-py/cru/service/_template.py | 18 | ||||
| -rw-r--r-- | tools/cru-py/cru/template.py | 166 | ||||
| -rw-r--r-- | tools/cru-py/poetry.lock | 191 | ||||
| -rw-r--r-- | tools/cru-py/pyproject.toml | 1 | 
9 files changed, 436 insertions, 163 deletions
| diff --git a/tools/cru-py/.gitignore b/tools/cru-py/.gitignore index 9f7550b..f5833b1 100644 --- a/tools/cru-py/.gitignore +++ b/tools/cru-py/.gitignore @@ -1,2 +1,3 @@  __pycache__  .venv +.mypy_cache diff --git a/tools/cru-py/.python-version b/tools/cru-py/.python-version index 37504c5..2c07333 100644 --- a/tools/cru-py/.python-version +++ b/tools/cru-py/.python-version @@ -1 +1 @@ -3.11
 +3.11 diff --git a/tools/cru-py/cru/_iter.py b/tools/cru-py/cru/_iter.py index 8f58561..f9683ca 100644 --- a/tools/cru-py/cru/_iter.py +++ b/tools/cru-py/cru/_iter.py @@ -445,6 +445,9 @@ class CruIterator(Generic[_T]):          return result +    def join_str(self: CruIterator[str], separator: str) -> str: +        return separator.join(self) +  class CruIterMixin(Generic[_T]):      def cru_iter(self: Iterable[_T]) -> CruIterator[_T]: diff --git a/tools/cru-py/cru/parsing.py b/tools/cru-py/cru/parsing.py index 1d2fa7f..c31ce35 100644 --- a/tools/cru-py/cru/parsing.py +++ b/tools/cru-py/cru/parsing.py @@ -1,6 +1,8 @@  from __future__ import annotations  from abc import ABCMeta, abstractmethod +from dataclasses import dataclass +from enum import Enum  from typing import NamedTuple, TypeAlias, TypeVar, Generic, NoReturn, Callable  from ._error import CruException @@ -9,6 +11,102 @@ from ._iter import CruIterable  _T = TypeVar("_T") +class StrParseStream: +    class MemStackEntry(NamedTuple): +        pos: int +        lineno: int + +    class MemStackPopStr(NamedTuple): +        text: str +        lineno: int + +    def __init__(self, text: str) -> None: +        self._text = text +        self._pos = 0 +        self._lineno = 1 +        self._length = len(self._text) +        self._valid_pos_range = range(0, self.length + 1) +        self._valid_offset_range = range(-self.length, self.length + 1) +        self._mem_stack: CruIterable.IterList[StrParseStream.MemStackEntry] = ( +            CruIterable.IterList() +        ) + +    @property +    def text(self) -> str: +        return self._text + +    @property +    def length(self) -> int: +        return self._length + +    @property +    def valid_pos_range(self) -> range: +        return self._valid_pos_range + +    @property +    def valid_offset_range(self) -> range: +        return self._valid_offset_range + +    @property +    def pos(self) -> int: +        return self._pos + +    @property +    def lineno(self) -> int: +        return self._lineno + +    @property +    def eof(self) -> bool: +        return self._pos == self.length + +    def peek(self, length: int) -> str: +        real_length = min(length, self.length - self._pos) +        new_position = self._pos + real_length +        text = self._text[self._pos : new_position] +        return text + +    def read(self, length: int) -> str: +        text = self.peek(length) +        self._pos += len(text) +        self._lineno += text.count("\n") +        return text + +    def skip(self, length: int) -> None: +        self.read(length) + +    def peek_str(self, text: str) -> bool: +        if self.pos + len(text) > self.length: +            return False +        for offset in range(len(text)): +            if self._text[self.pos + offset] != text[offset]: +                return False +        return True + +    def read_str(self, text: str) -> bool: +        if not self.peek_str(text): +            return False +        self._pos += len(text) +        self._lineno += text.count("\n") +        return True + +    @property +    def mem_stack(self) -> CruIterable.IterList[MemStackEntry]: +        return self._mem_stack + +    def push_mem(self) -> None: +        self.mem_stack.append(self.MemStackEntry(self.pos, self.lineno)) + +    def pop_mem(self) -> MemStackEntry: +        return self.mem_stack.pop() + +    def pop_mem_str(self, strip_end: int = 0) -> MemStackPopStr: +        old = self.pop_mem() +        assert self.pos >= old.pos +        return self.MemStackPopStr( +            self._text[old.pos : self.pos - strip_end], old.lineno +        ) + +  class ParseError(CruException, Generic[_T]):      def __init__(          self, @@ -96,3 +194,97 @@ class SimpleLineConfigParser(Parser[SimpleLineConfigParserResult]):          result = SimpleLineConfigParserResult()          self._parse(text, lambda item: result.append(item))          return result + + +class _StrWrapperVarParserTokenKind(Enum): +    TEXT = "TEXT" +    VAR = "VAR" + + +@dataclass +class _StrWrapperVarParserToken: +    kind: _StrWrapperVarParserTokenKind +    value: str +    line_number: int + +    @property +    def is_text(self) -> bool: +        return self.kind is _StrWrapperVarParserTokenKind.TEXT + +    @property +    def is_var(self) -> bool: +        return self.kind is _StrWrapperVarParserTokenKind.VAR + +    @staticmethod +    def from_mem_str( +        kind: _StrWrapperVarParserTokenKind, mem_str: StrParseStream.MemStackPopStr +    ) -> _StrWrapperVarParserToken: +        return _StrWrapperVarParserToken(kind, mem_str.text, mem_str.lineno) + +    def __repr__(self) -> str: +        return f"VAR: {self.value}" if self.is_var else "TEXT: ..." + + +class _StrWrapperVarParserResult(CruIterable.IterList[_StrWrapperVarParserToken]): +    pass + + +class StrWrapperVarParser(Parser[_StrWrapperVarParserResult]): +    TokenKind: TypeAlias = _StrWrapperVarParserTokenKind +    Token: TypeAlias = _StrWrapperVarParserToken +    Result: TypeAlias = _StrWrapperVarParserResult + +    def __init__(self, wrapper: str): +        super().__init__(f"StrWrapperVarParser({wrapper})") +        self._wrapper = wrapper + +    @property +    def wrapper(self) -> str: +        return self._wrapper + +    def parse(self, text: str) -> Result: +        result = self.Result() + +        class _State(Enum): +            TEXT = "TEXT" +            VAR = "VAR" + +        state = _State.TEXT +        stream = StrParseStream(text) +        stream.push_mem() + +        while True: +            if stream.eof: +                break + +            if stream.read_str(self.wrapper): +                if state is _State.TEXT: +                    result.append( +                        self.Token.from_mem_str( +                            self.TokenKind.TEXT, stream.pop_mem_str(len(self.wrapper)) +                        ) +                    ) +                    state = _State.VAR +                    stream.push_mem() +                else: +                    result.append( +                        self.Token.from_mem_str( +                            self.TokenKind.VAR, +                            stream.pop_mem_str(len(self.wrapper)), +                        ) +                    ) +                    state = _State.TEXT +                    stream.push_mem() + +                continue + +            stream.skip(1) + +        if state is _State.VAR: +            raise ParseError("Text ended without closing variable.", self, text) + +        mem_str = stream.pop_mem_str() +        if len(mem_str.text) != 0: +            result.append(self.Token.from_mem_str(self.TokenKind.TEXT, mem_str)) + +        return result diff --git a/tools/cru-py/cru/service/_nginx.py b/tools/cru-py/cru/service/_nginx.py index e0a9c60..6c77971 100644 --- a/tools/cru-py/cru/service/_nginx.py +++ b/tools/cru-py/cru/service/_nginx.py @@ -54,32 +54,19 @@ class NginxManager(AppCommandFeatureProvider):      def _get_domains_from_text(self, text: str) -> set[str]:          domains: set[str] = set()          regex = re.compile(r"server_name\s+(\S+)\s*;") -        domain_variable_str = f"${self._domain_config_name}" -        brace_domain_variable_regex = re.compile( -            r"\$\{\s*" + self._domain_config_name + r"\s*\}" -        )          for match in regex.finditer(text): -            domain_part = match.group(1) -            if domain_variable_str in domain_part: -                domains.add(domain_part.replace(domain_variable_str, self.root_domain)) -                continue -            m = brace_domain_variable_regex.search(domain_part) -            if m: -                domains.add(domain_part.replace(m.group(0), self.root_domain)) -                continue -            domains.add(domain_part) +            domains.add(match[1])          return domains -    def _get_nginx_conf_template_text(self) -> str: -        template_manager = self.app.get_feature(TemplateManager) +    def _join_generated_nginx_conf_text(self) -> str:          text = "" -        for path, template in template_manager.template_tree.templates: -            if path.as_posix().startswith("nginx/"): -                text += template.raw_text +        template_manager = self.app.get_feature(TemplateManager) +        for nginx_conf in template_manager.generate(): +            text += nginx_conf[1]          return text      def _get_domains(self) -> list[str]: -        text = self._get_nginx_conf_template_text() +        text = self._join_generated_nginx_conf_text()          domains = list(self._get_domains_from_text(text))          domains.remove(self.root_domain)          return [self.root_domain, *domains] diff --git a/tools/cru-py/cru/service/_template.py b/tools/cru-py/cru/service/_template.py index 170116c..1381700 100644 --- a/tools/cru-py/cru/service/_template.py +++ b/tools/cru-py/cru/service/_template.py @@ -1,8 +1,8 @@  from argparse import Namespace +from pathlib import Path  import shutil -from cru import CruIterator -from cru.template import TemplateTree +from cru.template import TemplateTree, CruStrWrapperTemplate  from ._base import AppCommandFeatureProvider, AppFeaturePath  from ._config import ConfigManager @@ -16,7 +16,7 @@ class TemplateManager(AppCommandFeatureProvider):      def setup(self) -> None:          self._templates_dir = self.app.add_path("templates", True)          self._generated_dir = self.app.add_path("generated", True) -        self._template_tree: TemplateTree | None = None +        self._template_tree: TemplateTree[CruStrWrapperTemplate] | None = None      @property      def prefix(self) -> str: @@ -31,20 +31,24 @@ class TemplateManager(AppCommandFeatureProvider):          return self._generated_dir      @property -    def template_tree(self) -> TemplateTree: +    def template_tree(self) -> TemplateTree[CruStrWrapperTemplate]:          if self._template_tree is None:              return self.reload()          return self._template_tree      def reload(self) -> TemplateTree:          self._template_tree = TemplateTree( -            self.prefix, self.templates_dir.full_path_str +            lambda text: CruStrWrapperTemplate(text), self.templates_dir.full_path_str          )          return self._template_tree      def _print_file_lists(self) -> None: -        for file in CruIterator(self.template_tree.templates).transform(lambda t: t[0]): -            print(file.as_posix()) +        for path, template in self.template_tree.templates: +            print(f"[{template.variable_count}]", path.as_posix()) + +    def generate(self) -> list[tuple[Path, str]]: +        config_manager = self.app.get_feature(ConfigManager) +        return self.template_tree.generate(config_manager.get_str_dict())      def _generate_files(self, dry_run: bool) -> None:          config_manager = self.app.get_feature(ConfigManager) diff --git a/tools/cru-py/cru/template.py b/tools/cru-py/cru/template.py index 6749cab..35d68ac 100644 --- a/tools/cru-py/cru/template.py +++ b/tools/cru-py/cru/template.py @@ -1,74 +1,124 @@ -from collections.abc import Mapping -import os -import os.path +from abc import ABCMeta, abstractmethod +from collections.abc import Callable, Mapping  from pathlib import Path  from string import Template +from typing import Generic, TypeVar  from ._iter import CruIterator  from ._error import CruException +from .parsing import StrWrapperVarParser +  class CruTemplateError(CruException):      pass -class CruTemplate: +class CruTemplateBase(metaclass=ABCMeta): +    def __init__(self, text: str): +        self._text = text +        self._variables: set[str] | None = None + +    @abstractmethod +    def _get_variables(self) -> set[str]: +        raise NotImplementedError() + +    @property +    def text(self) -> str: +        return self._text + +    @property +    def variables(self) -> set[str]: +        if self._variables is None: +            self._variables = self._get_variables() +        return self._variables + +    @property +    def variable_count(self) -> int: +        return len(self.variables) + +    @property +    def has_variables(self) -> bool: +        return self.variable_count > 0 + +    @abstractmethod +    def _do_generate(self, mapping: dict[str, str]) -> str: +        raise NotImplementedError() + +    def generate(self, mapping: Mapping[str, str], allow_extra: bool = True) -> str: +        values = dict(mapping) +        if not self.variables <= set(values.keys()): +            raise CruTemplateError("Missing variables.") +        if not allow_extra and not set(values.keys()) <= self.variables: +            raise CruTemplateError("Extra variables.") +        return self._do_generate(values) + + +class CruTemplate(CruTemplateBase):      def __init__(self, prefix: str, text: str): +        super().__init__(text)          self._prefix = prefix          self._template = Template(text) -        self._variables = ( + +    def _get_variables(self) -> set[str]: +        return (              CruIterator(self._template.get_identifiers()) -            .filter(lambda i: i.startswith(self._prefix)) +            .filter(lambda i: i.startswith(self.prefix))              .to_set()          ) -        self._all_variables = set(self._template.get_identifiers())      @property      def prefix(self) -> str:          return self._prefix      @property -    def raw_text(self) -> str: -        return self._template.template - -    @property      def py_template(self) -> Template:          return self._template      @property -    def variables(self) -> set[str]: -        return self._variables - -    @property      def all_variables(self) -> set[str]: -        return self._all_variables +        return set(self._template.get_identifiers()) + +    def _do_generate(self, mapping: dict[str, str]) -> str: +        return self._template.safe_substitute(mapping) + + +class CruStrWrapperTemplate(CruTemplateBase): +    def __init__(self, text: str, wrapper: str = "@@"): +        super().__init__(text) +        self._wrapper = wrapper +        self._tokens: StrWrapperVarParser.Result      @property -    def has_variables(self) -> bool: -        """ -        If the template does not has any variables that starts with the given prefix, -        it returns False. This usually indicates that the template is not a real -        template and should be copied as is. Otherwise, it returns True. +    def wrapper(self) -> str: +        return self._wrapper + +    def _get_variables(self): +        self._tokens = StrWrapperVarParser(self.wrapper).parse(self.text) +        return ( +            self._tokens.cru_iter() +            .filter(lambda t: t.is_var) +            .map(lambda t: t.value) +            .to_set() +        ) -        This can be used as a guard to prevent invalid templates created accidentally -        without notice. -        """ -        return len(self.variables) > 0 +    def _do_generate(self, mapping): +        return ( +            self._tokens.cru_iter() +            .map(lambda t: mapping[t.value] if t.is_var else t.value) +            .join_str("") +        ) -    def generate(self, mapping: Mapping[str, str], allow_extra: bool = True) -> str: -        values = dict(mapping) -        if not self.variables <= set(values.keys()): -            raise CruTemplateError("Missing variables.") -        if not allow_extra and not set(values.keys()) <= self.variables: -            raise CruTemplateError("Extra variables.") -        return self._template.safe_substitute(values) +_Template = TypeVar("_Template", bound=CruTemplateBase) -class TemplateTree: + +class TemplateTree(Generic[_Template]):      def __init__(          self, -        prefix: str, +        template_generator: Callable[[str], _Template],          source: str, +        *,          template_file_suffix: str | None = ".template",      ):          """ @@ -80,18 +130,14 @@ class TemplateTree:          If either case is false, it generally means whether the file is a template is          wrongly handled.          """ -        self._prefix = prefix -        self._files: list[tuple[Path, CruTemplate]] = [] +        self._template_generator = template_generator +        self._files: list[tuple[Path, _Template]] = []          self._source = source          self._template_file_suffix = template_file_suffix          self._load()      @property -    def prefix(self) -> str: -        return self._prefix - -    @property -    def templates(self) -> list[tuple[Path, CruTemplate]]: +    def templates(self) -> list[tuple[Path, _Template]]:          return self._files      @property @@ -103,13 +149,14 @@ class TemplateTree:          return self._template_file_suffix      @staticmethod -    def _scan_files(root_path: str) -> list[Path]: +    def _scan_files(root: str) -> list[Path]: +        root_path = Path(root)          result: list[Path] = [] -        for root, _dirs, files in os.walk(root_path): -            for file in files: -                path = Path(root, file) -                path = path.relative_to(root_path) -                result.append(Path(path)) +        for path in root_path.glob("**/*"): +            if not path.is_file(): +                continue +            path = path.relative_to(root_path) +            result.append(Path(path))          return result      def _load(self) -> None: @@ -118,7 +165,7 @@ class TemplateTree:              template_file = Path(self.source) / file_path              with open(template_file, "r") as f:                  content = f.read() -            template = CruTemplate(self.prefix, content) +            template = self._template_generator(content)              if self.template_file_suffix is not None:                  should_be_template = file_path.name.endswith(self.template_file_suffix)                  if should_be_template and not template.has_variables: @@ -136,18 +183,25 @@ class TemplateTree:              s.update(template.variables)          return s -    def generate_to( -        self, destination: str, variables: Mapping[str, str], dry_run: bool -    ) -> None: -        for file, template in self.templates: -            des = Path(destination) / file -            if self.template_file_suffix is not None and des.name.endswith( +    def generate(self, variables: Mapping[str, str]) -> list[tuple[Path, str]]: +        result: list[tuple[Path, str]] = [] +        for path, template in self.templates: +            if self.template_file_suffix is not None and path.name.endswith(                  self.template_file_suffix              ): -                des = des.parent / (des.name[: -len(self.template_file_suffix)]) +                path = path.parent / (path.name[: -len(self.template_file_suffix)])              text = template.generate(variables) -            if not dry_run: +            result.append((path, text)) +        return result + +    def generate_to( +        self, destination: str, variables: Mapping[str, str], dry_run: bool +    ) -> None: +        generated = self.generate(variables) +        if not dry_run: +            for path, text in generated: +                des = Path(destination) / path                  des.parent.mkdir(parents=True, exist_ok=True)                  with open(des, "w") as f:                      f.write(text) diff --git a/tools/cru-py/poetry.lock b/tools/cru-py/poetry.lock index 305aaee..4338200 100644 --- a/tools/cru-py/poetry.lock +++ b/tools/cru-py/poetry.lock @@ -1,80 +1,111 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
 -
 -[[package]]
 -name = "mypy"
 -version = "1.14.0"
 -description = "Optional static typing for Python"
 -optional = false
 -python-versions = ">=3.8"
 -files = [
 -    {file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"},
 -    {file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"},
 -    {file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"},
 -    {file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"},
 -    {file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"},
 -    {file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"},
 -    {file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"},
 -    {file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"},
 -    {file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"},
 -    {file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"},
 -    {file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"},
 -    {file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"},
 -    {file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"},
 -    {file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"},
 -    {file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"},
 -    {file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"},
 -    {file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"},
 -    {file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"},
 -    {file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"},
 -    {file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"},
 -    {file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"},
 -    {file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"},
 -    {file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"},
 -    {file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"},
 -    {file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"},
 -    {file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"},
 -    {file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"},
 -    {file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"},
 -    {file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"},
 -    {file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"},
 -    {file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"},
 -    {file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"},
 -]
 -
 -[package.dependencies]
 -mypy_extensions = ">=1.0.0"
 -typing_extensions = ">=4.6.0"
 -
 -[package.extras]
 -dmypy = ["psutil (>=4.0)"]
 -faster-cache = ["orjson"]
 -install-types = ["pip"]
 -mypyc = ["setuptools (>=50)"]
 -reports = ["lxml"]
 -
 -[[package]]
 -name = "mypy-extensions"
 -version = "1.0.0"
 -description = "Type system extensions for programs checked with the mypy type checker."
 -optional = false
 -python-versions = ">=3.5"
 -files = [
 -    {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
 -    {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
 -]
 -
 -[[package]]
 -name = "typing-extensions"
 -version = "4.12.2"
 -description = "Backported and Experimental Type Hints for Python 3.8+"
 -optional = false
 -python-versions = ">=3.8"
 -files = [
 -    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
 -    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
 -]
 -
 -[metadata]
 -lock-version = "2.0"
 -python-versions = "^3.11"
 -content-hash = "34a84c9f444021c048be3a70dbb3246bb73c4e7e8f0cc980b8050debcf21a6f9"
 +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "mypy" +version = "1.15.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ +    {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, +    {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, +    {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, +    {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, +    {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, +    {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, +    {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, +    {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, +    {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, +    {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, +    {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, +    {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, +    {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, +    {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, +    {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, +    {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, +    {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, +    {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, +    {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, +    {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, +    {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, +    {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, +    {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, +    {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, +    {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, +    {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, +    {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, +    {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, +    {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, +    {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, +    {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, +    {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ +    {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, +    {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "ruff" +version = "0.9.6" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ +    {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, +    {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, +    {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, +    {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, +    {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, +    {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, +    {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, +    {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, +    {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, +    {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, +    {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, +    {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ +    {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, +    {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.11" +content-hash = "674a21dbda993a1ee761e2e6e2f13ccece8289336a83fd0a154285eac48f3a76" diff --git a/tools/cru-py/pyproject.toml b/tools/cru-py/pyproject.toml index e5e7f09..0ce2c60 100644 --- a/tools/cru-py/pyproject.toml +++ b/tools/cru-py/pyproject.toml @@ -17,6 +17,7 @@ python = "^3.11"  [tool.poetry.group.dev.dependencies]  mypy = "^1.13.0" +ruff = "^0.9.6"  [tool.ruff.lint]  select = ["E", "F", "B"] | 
