From d062447e6d207d4fb343365a0f0f8de0cb98d0eb Mon Sep 17 00:00:00 2001 From: shmyga Date: Tue, 4 Aug 2026 22:08:28 +0300 Subject: [PATCH] feat: use jinja --- .pylintrc | 3 +- project_manager/common/command.py | 7 - project_manager/entry.py | 7 - project_manager/manager.py | 7 +- project_manager/pipe/__init__.py | 3 - project_manager/pipe/core.py | 87 ------------- project_manager/pipe/type/__init__.py | 0 project_manager/pipe/type/equal.py | 16 --- project_manager/pipe/type/file.py | 54 -------- project_manager/pipe/type/get.py | 32 ----- project_manager/pipe/type/not_.py | 13 -- project_manager/pipe/type/search.py | 20 --- project_manager/preset/default/docker.toml | 38 ++++-- project_manager/preset/default/nodejs.toml | 14 +- project_manager/preset/default/python.toml | 40 +++--- project_manager/preset/detector.py | 122 ++++++++++++++++++ project_manager/preset/entry.py | 45 ------- .../preset/{resolve.py => loader.py} | 24 ++-- project_manager/preset/model.py | 9 +- project_manager/util.py | 17 --- tests/test_entry.py | 22 ++-- 21 files changed, 204 insertions(+), 376 deletions(-) delete mode 100644 project_manager/pipe/__init__.py delete mode 100644 project_manager/pipe/core.py delete mode 100644 project_manager/pipe/type/__init__.py delete mode 100644 project_manager/pipe/type/equal.py delete mode 100644 project_manager/pipe/type/file.py delete mode 100644 project_manager/pipe/type/get.py delete mode 100644 project_manager/pipe/type/not_.py delete mode 100644 project_manager/pipe/type/search.py create mode 100644 project_manager/preset/detector.py delete mode 100644 project_manager/preset/entry.py rename project_manager/preset/{resolve.py => loader.py} (72%) diff --git a/.pylintrc b/.pylintrc index 0d53fc3..989562a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -8,7 +8,8 @@ disable= relative-beyond-top-level, subprocess-run-check, bare-except, - fixme + fixme, + unspecified-encoding [FORMAT] max-line-length=120 diff --git a/project_manager/common/command.py b/project_manager/common/command.py index d153f64..a0fad09 100644 --- a/project_manager/common/command.py +++ b/project_manager/common/command.py @@ -1,10 +1,3 @@ -__all__ = ( - "CommandError", - "CommandLogger", - "CommandExecutor", -) - - import logging import os import subprocess diff --git a/project_manager/entry.py b/project_manager/entry.py index d5dc20a..9e5a9ce 100644 --- a/project_manager/entry.py +++ b/project_manager/entry.py @@ -4,13 +4,6 @@ from typing import Iterable, NamedTuple from .common.command import CommandExecutor -__all__ = ( - "TaskKey", - "Entry", - "EntryDetector", - "EntryResolver", -) - logger = logging.getLogger("entry") diff --git a/project_manager/manager.py b/project_manager/manager.py index d249f8d..d19b168 100644 --- a/project_manager/manager.py +++ b/project_manager/manager.py @@ -5,15 +5,14 @@ from typing import Iterable from .common.command import CommandError from .entry import Entry, EntryResolver -from .preset.entry import PresetEntryDetector - -__all__ = ("ProjectManager",) +from .preset.detector import PresetEntryDetector logger = logging.getLogger("project") class ProjectManager: - DEFAULT_IGNORE = ["node_modules", ".venv", ".git", "tests"] + # TODO: from .gitignore? + DEFAULT_IGNORE = ["node_modules", ".venv", ".git", "tests", "__pycache__"] _logger = logger def __init__(self, basepath: Path): diff --git a/project_manager/pipe/__init__.py b/project_manager/pipe/__init__.py deleted file mode 100644 index 1cd4f2c..0000000 --- a/project_manager/pipe/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .core import PIPE_REGISTRY - -PIPE_REGISTRY.register_module("project_manager.pipe.type") diff --git a/project_manager/pipe/core.py b/project_manager/pipe/core.py deleted file mode 100644 index 483b23e..0000000 --- a/project_manager/pipe/core.py +++ /dev/null @@ -1,87 +0,0 @@ -import importlib -import re -from pathlib import Path -from typing import Any, Generic, TypeVar - -__all__ = ("Pipe", "PipeRegistry", "PipeParser", "PipeRunner", "PIPE_REGISTRY") - -I = TypeVar("I") -O = TypeVar("O") - - -class Pipe(Generic[I, O]): - type: str - - def apply(self, value: I, basepath: Path) -> O | None: - raise NotImplementedError - - -class PipeRegistry: - def __init__(self): - self._pipes = {} - - def register(self, pipe_type: type[Pipe]): - self._pipes[pipe_type.type] = pipe_type - - def register_module(self, module_name: str): - module = importlib.import_module(module_name) - modulepath = Path(module.__file__).parent - # pylint:disable=too-many-nested-blocks - for item in modulepath.iterdir(): - if item.suffix == ".py" and not item.name.startswith("__"): - sub_module = importlib.import_module(f"{module_name}.{item.stem}") - for v in sub_module.__dict__.values(): - if isinstance(v, type): - for baseclass in v.__bases__: - if baseclass == Pipe: - self.register(v) - - def get(self, pipe_type: str) -> type[Pipe]: - return self._pipes[pipe_type] - - -class PipeParser: - _PIPE_PATTERN = re.compile("^(\\w+)\\((.*)\\)") - - def parse(self, pipeline: str) -> list[Pipe]: - chain = [item.strip() for item in pipeline.split("|")] - result = [] - for part in chain: - matched = self._PIPE_PATTERN.match(part) - if matched: - pipe_name = matched.group(1) - pipe_args = [item.strip() for item in matched.group(2).split(",")] - else: - pipe_name = part.strip() - pipe_args = [] - pipe_type = PIPE_REGISTRY.get(pipe_name) - result.append(pipe_type(*pipe_args)) - return result - - -class PipeRunner: - def __init__(self, basepath: Path, parser: PipeParser | None = None): - self._basepath = basepath - self._parser = parser or PipeParser() - - def run(self, pipes: list[Pipe]) -> Any: - result = None - for pipe in pipes: - result = pipe.apply(result, self._basepath) - if result is None: - break - return result - - def resolve_value(self, pipeline: str) -> Any: - pipe = self._parser.parse(pipeline) - return self.run(pipe) - - def check_conditions(self, conditions: list[str] | None) -> bool: - if conditions: - for condition in conditions: - if not self.resolve_value(condition): - return False - return True - - -PIPE_REGISTRY = PipeRegistry() diff --git a/project_manager/pipe/type/__init__.py b/project_manager/pipe/type/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/project_manager/pipe/type/equal.py b/project_manager/pipe/type/equal.py deleted file mode 100644 index 3336f70..0000000 --- a/project_manager/pipe/type/equal.py +++ /dev/null @@ -1,16 +0,0 @@ -from pathlib import Path -from typing import Any - -from ..core import Pipe - -__all__ = ("EqualPipe",) - - -class EqualPipe(Pipe[Any, bool]): - type = "equal" - - def __init__(self, value: Any): - self._value = value - - def apply(self, value: Any, basepath: Path) -> bool: - return self._value == value diff --git a/project_manager/pipe/type/file.py b/project_manager/pipe/type/file.py deleted file mode 100644 index 1b5f373..0000000 --- a/project_manager/pipe/type/file.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -from pathlib import Path -import tomllib -from typing import Any - -import dotenv - -from ..core import Pipe - -__all__ = ( - "FileData", - "FilePipe", -) - - -class FileData: - - def __init__(self, filepath: Path, loader: str | None = None): - self._filepath = filepath - self._loader = loader - self._data = None - - def _load_data(self): - if self._filepath.suffix == ".toml": - return tomllib.loads(self._filepath.read_text()) - elif self._filepath.suffix == ".json": - return json.loads(self._filepath.read_text()) - elif self._filepath.suffix == ".env" or self._filepath.name == ".env": - return dotenv.dotenv_values(self._filepath) - return self._filepath.read_text() - - def get(self, key: str): - if self._data is None and self._filepath.exists(): - self._data = self._load_data() - if self._data: - return self._data.get(key) - return None - - def __bool__(self): - return self._filepath.exists() - - -class FilePipe(Pipe[None, Any]): - type = "file" - - def __init__(self, filename: str | None, loader: str | None = None): - self._filename = filename - self._loader = loader - - def apply(self, value: str | None, basepath: Path) -> Any | None: - filepath = Path(value or self._filename) # TODO: raise none filepath exception - if not filepath.is_absolute(): - filepath = basepath / filepath - return FileData(filepath, self._loader) diff --git a/project_manager/pipe/type/get.py b/project_manager/pipe/type/get.py deleted file mode 100644 index 3a4899b..0000000 --- a/project_manager/pipe/type/get.py +++ /dev/null @@ -1,32 +0,0 @@ -from pathlib import Path -from typing import Any - -from ..core import Pipe - -__all__ = ("GetPipe", "KeyPipe") - - -class GetPipe(Pipe[dict, Any]): - type = "get" - - def __init__(self, keypath: str): - self._keypath = keypath - - def apply(self, value: dict, basepath: Path) -> Any | None: - path = self._keypath.split(".") - result = value.get(path[0]) - for key in path[1:]: - if result: - result = result.get(key) - return result - - -class KeyPipe(Pipe[dict, Any]): - type = "key" - - def __init__(self, keyindex: str | None): - self._keyindex = int(keyindex) if keyindex else 0 - - def apply(self, value: dict, basepath: Path) -> Any | None: - keys = list(value.keys()) - return keys[self._keyindex] diff --git a/project_manager/pipe/type/not_.py b/project_manager/pipe/type/not_.py deleted file mode 100644 index 8bcfcc9..0000000 --- a/project_manager/pipe/type/not_.py +++ /dev/null @@ -1,13 +0,0 @@ -from pathlib import Path -from typing import Any - -from ..core import Pipe - -__all__ = ("NotPipe",) - - -class NotPipe(Pipe[Any, bool]): - type = "not" - - def apply(self, value: Any, basepath: Path) -> bool: - return not value diff --git a/project_manager/pipe/type/search.py b/project_manager/pipe/type/search.py deleted file mode 100644 index b005fe9..0000000 --- a/project_manager/pipe/type/search.py +++ /dev/null @@ -1,20 +0,0 @@ -import re -from pathlib import Path - -from ..core import Pipe - -__all__ = ("SearchPipe",) - - -class SearchPipe(Pipe[str, str]): - type = "search" - - def __init__(self, patter: str, position: str = "1"): - self._patter = patter - self._position = int(position) - - def apply(self, value: str, basepath: Path) -> str | None: - matched = re.compile(self._patter).search(value) - if matched: - return matched.group(self._position) - return None diff --git a/project_manager/preset/default/docker.toml b/project_manager/preset/default/docker.toml index 184e091..3b7cb4e 100644 --- a/project_manager/preset/default/docker.toml +++ b/project_manager/preset/default/docker.toml @@ -1,16 +1,36 @@ [presets.docker] -conditions = ["file(Dockerfile)"] +conditions = ["{{ 'Dockerfile' | file }}"] +variables.docker_name = "{{ 'pyproject.toml' | read | get('project.name') or 'package.json' | read | get('name') }}" +variables.docker_version = "{{ 'pyproject.toml' | read | get('project.version') or 'package.json' | read | get('version') }}" +variables.docker_repo = "{{ '.env' | read | get('DOCKER_REPO') }}" +variables.docker_group = "{{ '.env' | read | get('DOCKER_GROUP') }}" +variables.docker_image = "{{ docker_group }}/{{ docker_name }}" +variables.docker_tag = "{{ '.env' | read | get('DOCKER_TAG') or 'latest' }}" # build [presets.docker.tasks.build.default] -variables.docker_target = "file(.env) | get(DOCKER_PROJECTS)" -variables.docker_name = "file(pyproject.toml) | get(project.name)" -variables.docker_group = "file(.env) | get(DOCKER_GROUP)" +commands = ["docker build -t {{ docker_image }} . -f Dockerfile"] + +# publish version +[presets.docker.tasks.publish.version] +conditions = ["{{ docker_version }}"] commands = [ - # "docker build --target {docker_target} -t {docker_group}/{docker_name} . -f Dockerfile", - "docker build -t {docker_group}/{docker_name} . -f Dockerfile", + "docker tag {{ docker_image }} {{ docker_repo }}/{{ docker_image }}:{{ docker_version }}", + "docker push {{ docker_repo }}/{{ docker_image }}:{{ docker_version }}", ] -# publish -[presets.docker.tasks.publish.default] -commands = ["docker publish"] +# publish tag +[presets.docker.tasks.publish.tag] +conditions = ["{{ docker_tag }}"] +commands = [ + "docker tag {{ docker_image }} {{ docker_repo }}/{{ docker_image }}:{{ docker_tag }}", + "docker push {{ docker_repo }}/{{ docker_image }}:{{ docker_tag }}", +] + +# save +[presets.docker.tasks.save.default] +variables.docker_output = "./dist" +commands = [ + "mkdir -p {{ docker_output }}", + "docker save --output {{ docker_output }}/{{ docker_name }}-{{ docker_version }}.tar {{ docker_image }}", +] diff --git a/project_manager/preset/default/nodejs.toml b/project_manager/preset/default/nodejs.toml index f47e8bf..ddc8c10 100644 --- a/project_manager/preset/default/nodejs.toml +++ b/project_manager/preset/default/nodejs.toml @@ -1,26 +1,26 @@ [presets.nodejs] -conditions = ["file(package.json)"] +conditions = ["{{ 'package.json' | file }}"] [presets.nodejs.tasks.setup.npm] -conditions = ["file(package-lock.json)"] +conditions = ["{{ 'package-lock.json' | file }}"] commands = ["npm install-clean"] [presets.nodejs.tasks.setup.npm-legacy] -conditions = ["file(package-lock.json) | not"] +conditions = ["{{ not ('package-lock.json' | file) }}"] commands = ["npm install"] [presets.nodejs.tasks.test.jest] -conditions = ["file(node_modules/jest)"] +conditions = ["{{ 'node_modules/jest' | file }}"] commands = ["npx jest"] [presets.nodejs.tasks.build.npm] -conditions = ["file(package.json) | get(scripts.build)"] +conditions = ["{{ 'package.json' | read | get('scripts.build') }}"] commands = ["npm run build"] [presets.nodejs.tasks.publish.npm] -conditions = ["file(package.json) | get(scripts.publish)"] +conditions = ["{{ 'package.json' | read | get('scripts.publish') }}"] commands = ["npm run publish"] [presets.nodejs.tasks.clean.node_modules] -conditions = ["file(node_modules)"] +conditions = ["{{ 'node_modules' | file }}"] commands = ["rm -rf node_modules"] diff --git a/project_manager/preset/default/python.toml b/project_manager/preset/default/python.toml index b5c5ac8..041dd82 100644 --- a/project_manager/preset/default/python.toml +++ b/project_manager/preset/default/python.toml @@ -1,61 +1,55 @@ [presets.python] -conditions = ["file(pyproject.toml)"] +conditions = ["{{ 'pyproject.toml' | file }}"] +variables.python_poetry = "{{ 'pyproject.toml' | read | get('build-system.build-backend') == 'poetry.core.masonry.api' }}" # setup [presets.python.tasks.setup.poetry] -conditions = [ - "file(pyproject.toml) | get(build-system.build-backend) | equal(poetry.core.masonry.api)", -] -variables.python_version = "file(pyproject.toml) | get(project.requires-python) | search((\\d+.\\d+))" -commands = [ - "poetry env use {python_version}", - "poetry install", -] +conditions = ["{{ python_poetry }}"] +variables.python_version = "{{ 'pyproject.toml' | read | get('project.requires-python') | search('(\\d+.\\d+)') }}" +commands = ["poetry env use {{ python_version }}", "poetry install"] # build [presets.python.tasks.build.poetry] -conditions = [ - "file(pyproject.toml) | get(build-system.build-backend) | equal(poetry.core.masonry.api)", -] +conditions = ["{{ python_poetry }}"] commands = ["poetry build"] # publish [presets.python.tasks.publish.poetry] conditions = [ - "file(pyproject.toml) | get(build-system.build-backend) | equal(poetry.core.masonry.api)", - "file(poetry.toml) | get(repositories)", + "{{ python_poetry }}", + "{{ 'poetry.toml' | read | get('repositories') }}", ] -variables.python_repository = "file(poetry.toml) | get(repositories) | key(0)" -commands = ["poetry publish -r {python_repository}"] +variables.python_repository = "{{ 'poetry.toml' | read | get('repositories') | key(0) }}" +commands = ["poetry publish -r {{ python_repository }}"] # test [presets.python.tasks.test.pytest] -conditions = ["file(.venv/bin/pytest)"] +conditions = ["{{ '.venv/bin/pytest' | file }}"] commands = [".venv/bin/python -m pytest"] # lint [presets.python.tasks.lint.pylint] -conditions = ["file(.venv/bin/pylint)"] +conditions = ["{{ '.venv/bin/pylint' | file }}"] commands = [".venv/bin/pylint ./"] [presets.python.tasks.lint.isort] -conditions = ["file(.venv/bin/isort)"] +conditions = ["{{ '.venv/bin/isort' | file }}"] commands = [".venv/bin/isort --profile black --check-only ./"] [presets.python.tasks.lint.black] -conditions = ["file(.venv/bin/black)"] +conditions = ["{{ '.venv/bin/black' | file }}"] commands = [".venv/bin/black --check --diff ./"] # format [presets.python.tasks.format.isort] -conditions = ["file(.venv/bin/isort)"] +conditions = ["{{ '.venv/bin/isort' | file }}"] commands = [".venv/bin/isort --profile black ./"] [presets.python.tasks.format.black] -conditions = ["file(.venv/bin/black)"] +conditions = ["{{ '.venv/bin/isort' | file }}"] commands = [".venv/bin/black ./"] # clean [presets.python.tasks.clean.venv] -conditions = ["file(.venv)"] +conditions = ["{{ '.venv' | file }}"] commands = ["rm -rf .venv"] diff --git a/project_manager/preset/detector.py b/project_manager/preset/detector.py new file mode 100644 index 0000000..da3e2f9 --- /dev/null +++ b/project_manager/preset/detector.py @@ -0,0 +1,122 @@ +import json +import re +import tomllib +from collections import defaultdict +from pathlib import Path + +import dotenv +from jinja2 import Environment + +from ..entry import Entry, EntryDetector, TaskKey +from .loader import PresetLoader, PresetLoaderConfig +from .model import Variables + + +def _build_env(basepath: Path) -> Environment: + env = Environment() + + def do_file(filename: str) -> Path | None: + result = Path(filename) + if not result.is_absolute(): + result = basepath / result + if result.exists(): + return result + return None + + def do_read(filename: str) -> dict | None: + path = do_file(filename) + if path: + if path.suffix == ".toml": + return tomllib.loads(path.read_text()) + if path.suffix == ".json": + return json.loads(path.read_text()) + if path.suffix == ".env" or path.name == ".env": + return dotenv.dotenv_values(path) + return None + + def do_get(data: dict | None, keypath: str) -> str | None: + if not data: + return None + path = keypath.split(".") + result = data.get(path[0]) + for key in path[1:]: + if result: + result = result.get(key) + return result + + def do_search(data: str | None, pattern: str, position: int = 1) -> str | None: + if not data: + return None + matched = re.compile(pattern).search(data) + if matched: + return matched.group(position) + return None + + def do_key(data: dict | None, keyindex: int = 0) -> str | None: + if not data: + return None + keys = list(data.keys()) + return keys[keyindex] + + env.filters["file"] = do_file + env.filters["read"] = do_read + env.filters["get"] = do_get + env.filters["search"] = do_search + env.filters["key"] = do_key + return env + + +class PathProcessor: + def __init__(self, basepath: Path): + self._basepath = basepath + self._env = _build_env(basepath) + + def update_variables(self, templates: Variables, variables: Variables): + for k, v in templates.items(): + variables[k] = self._env.from_string(v).render(**variables) + + def fill_template(self, template: str, variables: Variables) -> str: + return self._env.from_string(template).render(**variables) + + def check_conditions(self, templates: list[str], variables: Variables) -> bool: + for template in templates: + value = self.fill_template(template, variables) + if value.lower() in ["false", "null", "none", "0", ""]: + return False + return True + + +class PresetEntryDetector(EntryDetector): + + def __init__(self, preset_loader_config: PresetLoaderConfig | None = None): + self._preset_loader_config = preset_loader_config + + def detect_entry(self, basepath: Path) -> Entry | None: + processor = PathProcessor(basepath) + preset = PresetLoader(basepath, self._preset_loader_config).load() + if preset.skip: + return None + tasks = defaultdict(list) + for tag, preset in preset.presets.items(): + preset_variables: Variables = {"entry_path": basepath} + if preset.variables: + processor.update_variables(preset.variables, preset_variables) + if preset.conditions and not processor.check_conditions( + preset.conditions, preset_variables + ): + continue + for task_name, task_pack in preset.tasks.items(): + for task in task_pack.values(): + if not task.conditions or processor.check_conditions( + task.conditions, preset_variables + ): + variables = preset_variables.copy() + if task.variables: + processor.update_variables(task.variables, variables) + tasks[TaskKey(task_name, tag)] += [ + processor.fill_template(command, variables) + for command in task.commands + ] + if tasks: + return Entry(tasks, basepath) + return None diff --git a/project_manager/preset/entry.py b/project_manager/preset/entry.py deleted file mode 100644 index 2a35afe..0000000 --- a/project_manager/preset/entry.py +++ /dev/null @@ -1,45 +0,0 @@ -from collections import defaultdict -from pathlib import Path - -from ..entry import Entry, EntryDetector, TaskKey -from ..pipe.core import PipeParser, PipeRunner -from .resolve import PresetResolver, PresetResolverConfig - -__all__ = ("PresetEntryDetector",) - - -class PresetEntryDetector(EntryDetector): - _PIPE_PARSER = PipeParser() - - def __init__(self, preset_resolver_config: PresetResolverConfig | None = None): - self._pipeparser = PipeParser() - self._preset_resolver_config = preset_resolver_config - - def detect_entry(self, basepath: Path) -> Entry | None: - preset = PresetResolver(basepath, self._preset_resolver_config).resolve_preset() - if preset.skip: - return None - piperunner = PipeRunner(basepath) - tasks = defaultdict(list) - for tag, preset in preset.presets.items(): - if not piperunner.check_conditions(preset.conditions): - continue - for task_name, task_pack in preset.tasks.items(): - for task in task_pack.values(): - if piperunner.check_conditions(task.conditions): - variables = { - "entry_path": basepath, - } - if task.variables: - variables.update( - { - k: piperunner.resolve_value(v) - for k, v in task.variables.items() - } - ) - tasks[TaskKey(task_name, tag)] += [ - command.format(**variables) for command in task.commands - ] - if tasks: - return Entry(tasks, basepath) - return None diff --git a/project_manager/preset/resolve.py b/project_manager/preset/loader.py similarity index 72% rename from project_manager/preset/resolve.py rename to project_manager/preset/loader.py index 6ee61a7..2d69a83 100644 --- a/project_manager/preset/resolve.py +++ b/project_manager/preset/loader.py @@ -1,33 +1,27 @@ +import tomllib from dataclasses import dataclass from pathlib import Path from typing import Iterable -import tomli - from ..util import merge_dicts from .model import Preset -__all__ = ( - "PresetResolverConfig", - "PresetResolver", -) - @dataclass -class PresetResolverConfig: +class PresetLoaderConfig: default: bool = True user: bool = True project: bool = True -class PresetResolver: +class PresetLoader: _source_cache: dict[Path, dict] = {} - def __init__(self, basepath: Path, config: PresetResolverConfig | None = None): + def __init__(self, basepath: Path, config: PresetLoaderConfig | None = None): self._basepath = basepath - self._config = config or PresetResolverConfig() + self._config = config or PresetLoaderConfig() - def iter_sources(self) -> Iterable[Path]: + def _iter_sources(self) -> Iterable[Path]: if self._config.default: yield from (Path(__file__).parent / "default").iterdir() if self._config.user: @@ -44,11 +38,11 @@ class PresetResolver: break projectpath = projectpath.parent - def resolve_preset(self) -> Preset: + def load(self) -> Preset: result_preset_data = {} - for filepath in self.iter_sources(): + for filepath in self._iter_sources(): if filepath not in self._source_cache: with open(filepath, "rb") as fd: - self._source_cache[filepath] = tomli.load(fd) + self._source_cache[filepath] = tomllib.load(fd) merge_dicts(result_preset_data, self._source_cache[filepath]) return Preset(**result_preset_data) diff --git a/project_manager/preset/model.py b/project_manager/preset/model.py index 9650367..f140df6 100644 --- a/project_manager/preset/model.py +++ b/project_manager/preset/model.py @@ -1,20 +1,17 @@ from pydantic import BaseModel -__all__ = ( - "TaskPreset", - "EntryPreset", - "Preset", -) +Variables = dict[str, str] class TaskPreset(BaseModel): conditions: list[str] | None = None - variables: dict[str, str] | None = None + variables: Variables | None = None commands: list[str] class EntryPreset(BaseModel): conditions: list[str] | None = None + variables: Variables | None = None tasks: dict[str, dict[str, TaskPreset]] diff --git a/project_manager/util.py b/project_manager/util.py index 363056d..3d787c0 100644 --- a/project_manager/util.py +++ b/project_manager/util.py @@ -1,20 +1,3 @@ -import importlib - -__all__ = ( - "reflect", - "merge_dicts", -) - - -def reflect(value: str): - if ":" in value: - module_name, item_name = value.split(":") - else: - module_name, item_name = "builtins", value - module = importlib.import_module(module_name) - return getattr(module, item_name) - - def merge_dicts(target, enhancer): for key, val in enhancer.items(): if key not in target: diff --git a/tests/test_entry.py b/tests/test_entry.py index 73f882e..d7f691f 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -5,21 +5,21 @@ from pathlib import Path import pytest from project_manager.entry import Entry, EntryDetector -from project_manager.preset.entry import PresetEntryDetector -from project_manager.preset.resolve import PresetResolverConfig +from project_manager.preset.detector import PresetEntryDetector +from project_manager.preset.loader import PresetLoaderConfig @pytest.fixture -def preset_resolver_config(): - return PresetResolverConfig( +def preset_loader_config(): + return PresetLoaderConfig( user=False, project=False, ) @pytest.fixture -def entry_detector(preset_resolver_config: PresetResolverConfig): - return PresetEntryDetector(preset_resolver_config) +def entry_detector(preset_loader_config: PresetLoaderConfig): + return PresetEntryDetector(preset_loader_config) @pytest.fixture @@ -31,18 +31,20 @@ def entry(entry_detector: EntryDetector, entry_path: Path, tmp_path: Path): @pytest.mark.parametrize("entry_path", [Path(__file__).parent / "python"]) def test_python_entry(entry: Entry | None, entry_detector: EntryDetector): assert entry is not None - assert {"setup", "build"} == entry._tasks.keys() + assert {"setup", "build"} == {k.name for k in entry._tasks.keys()} entry.run_task("setup") entry = entry_detector.detect_entry(entry._basepath) assert entry is not None - assert {"setup", "build", "lint", "format", "test", "clean"} == entry._tasks.keys() + assert {"setup", "build", "lint", "format", "test", "clean"} == { + k.name for k in entry._tasks.keys() + } @pytest.mark.parametrize("entry_path", [Path(__file__).parent / "nodejs"]) def test_nodejs_entry(entry: Entry | None, entry_detector: EntryDetector): assert entry is not None - assert {"setup", "build"} == entry._tasks.keys() + assert {"setup", "build"} == {k.name for k in entry._tasks.keys()} entry.run_task("setup") entry = entry_detector.detect_entry(entry._basepath) assert entry is not None - assert {"setup", "build", "test", "clean"} == entry._tasks.keys() + assert {"setup", "build", "test", "clean"} == {k.name for k in entry._tasks.keys()}