From b459eef34b7db8757b08fa31531d641665721188 Mon Sep 17 00:00:00 2001 From: shmyga Date: Fri, 2 Oct 2026 15:55:10 +0300 Subject: [PATCH] feat(preset): add gitea preset --- project_manager/common/template.py | 21 +++++++--- project_manager/common/util.py | 13 +++++++ project_manager/preset/default/config.toml | 14 ++++++- project_manager/preset/default/docker.toml | 14 ++++--- project_manager/preset/default/gitea.toml | 24 ++++++++++++ project_manager/preset/default/global.toml | 2 + project_manager/preset/detector.py | 45 ++++++++++++++-------- project_manager/preset/model.py | 4 +- 8 files changed, 106 insertions(+), 31 deletions(-) create mode 100644 project_manager/preset/default/gitea.toml diff --git a/project_manager/common/template.py b/project_manager/common/template.py index 6680eac..c6cb9f6 100644 --- a/project_manager/common/template.py +++ b/project_manager/common/template.py @@ -1,5 +1,4 @@ import json -import os import re import tomllib from pathlib import Path @@ -9,6 +8,7 @@ import dotenv from jinja2 import Environment from ..preset.model import Variables +from .util import get_environ def do_bool(data: str | None) -> bool: @@ -19,10 +19,6 @@ def do_bool(data: str | None) -> bool: return data.lower() not in ["false", "null", "none", "0", ""] -def do_env(key: str, default: str | None = None) -> str | None: - return os.environ.get(key=key.upper(), default=default) - - def do_not(value: Any | None) -> bool: return not do_bool(str(value)) @@ -31,6 +27,8 @@ def _build_env(basepath: Path) -> Environment: env = Environment() def do_file(filename: str) -> Path | None: + if not filename: + return None result = Path(filename) if not result.is_absolute(): result = basepath / result @@ -73,6 +71,11 @@ def _build_env(basepath: Path) -> Environment: keys = list(data.keys()) return keys[keyindex] + env_data = get_environ(basepath) + + def do_env(key: str, default: str | None = None) -> str | None: + return env_data.get(key.upper(), default) + env.filters["file"] = do_file env.filters["read"] = do_read env.filters["get"] = do_get @@ -91,7 +94,13 @@ class PathTemplate: def update_variables(self, templates: Variables, variables: Variables): for k, v in templates.items(): - variables[k] = self._env.from_string(v).render(**variables) + if isinstance(v, dict): + variables[k] = { + kk: self._env.from_string(vv).render(**variables) + for kk, vv in v.items() + } + else: + 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) diff --git a/project_manager/common/util.py b/project_manager/common/util.py index 6984842..47049c4 100644 --- a/project_manager/common/util.py +++ b/project_manager/common/util.py @@ -1,3 +1,9 @@ +import os +from pathlib import Path + +import dotenv + + def merge_dicts(target: dict, enhancer: dict) -> dict: for key, val in enhancer.items(): if key not in target: @@ -9,3 +15,10 @@ def merge_dicts(target: dict, enhancer: dict) -> dict: else: target[key] = val return target + + +def get_environ(path: Path) -> dict[str, str]: + result = os.environ.copy() + if (env_file := path / ".env").exists(): + result.update(dotenv.dotenv_values(env_file)) + return result diff --git a/project_manager/preset/default/config.toml b/project_manager/preset/default/config.toml index 9de8c58..53b6288 100644 --- a/project_manager/preset/default/config.toml +++ b/project_manager/preset/default/config.toml @@ -1,4 +1,16 @@ -[presets.config.tasks.setup.config] +[presets.config.tasks.setup.env] priority = 1 conditions = ["{{ ('.env-base' | file) and ('.env' | file | not ) }}"] commands = ["cp .env-base .env"] + +[presets.config.tasks.setup.env2] +priority = 1 +conditions = ["{{ ('.env.default' | file) and ('.env' | file | not ) }}"] +commands = ["cp .env.default .env"] + +[presets.config.tasks.setup.json] +priority = 1 +conditions = [ + "{{ ('config.example.json' | file) and ('config.json' | file | not ) }}", +] +commands = ["cp config.example.json config.json"] diff --git a/project_manager/preset/default/docker.toml b/project_manager/preset/default/docker.toml index 6ddbebf..1ec9eb1 100644 --- a/project_manager/preset/default/docker.toml +++ b/project_manager/preset/default/docker.toml @@ -1,15 +1,17 @@ [presets.docker] 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_name = "{{ project_name }}" +variables.docker_version = "{{ project_version }}" +variables.docker_repo = "{{ 'DOCKER_REPO' | env }}" +variables.docker_group = "{{ 'DOCKER_GROUP' | env }}" variables.docker_image = "{% if docker_group | bool %}{{ docker_group }}/{% endif %}{{ docker_name }}" -variables.docker_tag = "{{ '.env' | read | get('DOCKER_TAG') or 'latest' }}" +variables.docker_tag = "{{ 'DOCKER_TAG' | env('latest') }}" +variables.docker_target = "{{ 'DOCKER_TARGET' | env }}" +variables.docker_target_arg = "{{ ('--target ' + docker_target) if docker_target else '' }}" # build [presets.docker.tasks.build.default] -commands = ["docker build -t {{ docker_image }} . -f Dockerfile"] +commands = ["docker build {{ docker_target_arg }} -t {{ docker_image }} . -f Dockerfile"] # publish version [presets.docker.tasks.publish.version] diff --git a/project_manager/preset/default/gitea.toml b/project_manager/preset/default/gitea.toml new file mode 100644 index 0000000..ac91a6b --- /dev/null +++ b/project_manager/preset/default/gitea.toml @@ -0,0 +1,24 @@ +[presets.gitea] +variables.publish_repo = "{{ 'PUBLISH_REPO' | env }}" +variables.publish_repo_path = "{{ publish_repo }}/generic/{{ project_name }}/{{ project_version }}" +variables.publish_user = "{{ 'PUBLISH_USER' | env }}" +variables.publish_password = "{{ 'PUBLISH_PASSWORD' | env }}" +variables.publish_auth = "{{ publish_user }}:{{ publish_password }}" +variables.publish_artefacts = {} + +[presets.gitea.tasks.publish."{{ publish_artefacts | tojson }}"] +conditions = ["{{ item | file }}"] +variables.artefact_file = "{{ item }}" +variables.artefact_name = "{{ (artefact_file | file).name }}" +commands = [ + "curl --user \"{{ publish_auth }}\" -X DELETE \"{{ publish_repo_path }}/{{ artefact_name }}\"", + "curl --user \"{{ publish_auth }}\" --fail --upload-file \"{{ artefact_file }}\" \"{{ publish_repo_path }}/{{ artefact_name }}\"", +] + +[presets.gitea.tasks.publish.debian_repo] +conditions = ["{{ publish_artefacts.debian | file }}"] +variables.artefact_file = "{{ publish_artefacts.debian }}" +commands = [ + "curl --user \"{{ publish_auth }}\" -X DELETE \"{{ publish_repo }}/debian/pool/noble/main/{{ project_name }}/{{ project_version }}/all\"", + "curl --user \"{{ publish_auth }}\" --fail --upload-file \"{{ artefact_file }}\" \"{{ publish_repo }}/debian/pool/noble/main/upload\"", +] diff --git a/project_manager/preset/default/global.toml b/project_manager/preset/default/global.toml index 32e4e26..04d66bb 100644 --- a/project_manager/preset/default/global.toml +++ b/project_manager/preset/default/global.toml @@ -1,5 +1,7 @@ [presets.'*'] priority = 1000 conditions = ["{{ true }}"] +variables.project_name = "{{ 'pyproject.toml' | read | get('project.name') or 'package.json' | read | get('name') }}" +variables.project_version = "{{ 'pyproject.toml' | read | get('project.version') or 'package.json' | read | get('version') }}" variables.version = "{{ 'version' | env }}" tasks = {} diff --git a/project_manager/preset/detector.py b/project_manager/preset/detector.py index e425635..1dea1c5 100644 --- a/project_manager/preset/detector.py +++ b/project_manager/preset/detector.py @@ -1,3 +1,4 @@ +import json import operator from os import environ from pathlib import Path @@ -14,7 +15,7 @@ class PresetEntryDetector(EntryDetector): def __init__(self, preset_loader_config: PresetLoaderConfig | None = None): self._preset_loader_config = preset_loader_config - # pylint:disable=too-many-locals + # pylint:disable=too-many-locals,too-many-nested-blocks,too-many-branches def detect_entry(self, basepath: Path, rootpath: Path) -> Entry: template = PathTemplate(basepath) preset = PresetLoader(basepath, self._preset_loader_config).load() @@ -44,22 +45,32 @@ class PresetEntryDetector(EntryDetector): continue for task_name, task_pack in preset.tasks.items(): for sub_tag, task_preset in task_pack.items(): - if not task_preset.conditions or template.check_conditions( - task_preset.conditions, preset_variables - ): - variables = preset_variables.copy() - if task_preset.variables: - template.update_variables(task_preset.variables, variables) - task = Task( - key=TaskKey(task_name, tag, sub_tag), - commands=[ - template.fill_template(command, variables) - for command in task_preset.commands - ], - priority=task_preset.priority, - cwd=task_preset.cwd, - ) - tasks.append(task) + sub_tags = template.fill_template(sub_tag, preset_variables) + try: + sub_tags = json.loads(sub_tags) + except ValueError: + sub_tags = {sub_tags: None} + for i_sub_tag, i_sub_tag_value in sub_tags.items(): + if i_sub_tag_value: + preset_variables["item"] = i_sub_tag_value + if not task_preset.conditions or template.check_conditions( + task_preset.conditions, preset_variables + ): + variables = preset_variables.copy() + if task_preset.variables: + template.update_variables( + task_preset.variables, variables + ) + task = Task( + key=TaskKey(task_name, tag, i_sub_tag), + commands=[ + template.fill_template(command, variables) + for command in task_preset.commands + ], + priority=task_preset.priority, + cwd=task_preset.cwd, + ) + tasks.append(task) if tasks: tasks = sorted(tasks, key=operator.attrgetter("priority"), reverse=True) return Entry(tasks, basepath, rootpath) diff --git a/project_manager/preset/model.py b/project_manager/preset/model.py index 5af1adb..73a6942 100644 --- a/project_manager/preset/model.py +++ b/project_manager/preset/model.py @@ -1,6 +1,8 @@ +from typing import Any + from pydantic import BaseModel -Variables = dict[str, str] +Variables = dict[str, Any] class TaskPreset(BaseModel):