feat: add presets

This commit is contained in:
2023-03-16 15:07:11 +03:00
parent 23ca8c0cac
commit 8b718fda79
15 changed files with 357 additions and 142 deletions
Generated
+160 -3
View File
@@ -1,5 +1,24 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "attrs"
version = "22.2.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"},
{file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"},
]
[package.extras]
cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"]
dev = ["attrs[docs,tests]"]
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"]
tests = ["attrs[tests-no-zope]", "zope.interface"]
tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"]
[[package]]
name = "black"
version = "23.1.0"
@@ -77,6 +96,51 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "exceptiongroup"
version = "1.1.1"
description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
{file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "isort"
version = "5.12.0"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
]
[package.extras]
colors = ["colorama (>=0.4.3)"]
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
@@ -129,6 +193,99 @@ files = [
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
version = "1.10.6"
description = "Data validation and settings management using python type hints"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic-1.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9289065611c48147c1dd1fd344e9d57ab45f1d99b0fb26c51f1cf72cd9bcd31"},
{file = "pydantic-1.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c32b6bba301490d9bb2bf5f631907803135e8085b6aa3e5fe5a770d46dd0160"},
{file = "pydantic-1.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd9b9e98068fa1068edfc9eabde70a7132017bdd4f362f8b4fd0abed79c33083"},
{file = "pydantic-1.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c84583b9df62522829cbc46e2b22e0ec11445625b5acd70c5681ce09c9b11c4"},
{file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b41822064585fea56d0116aa431fbd5137ce69dfe837b599e310034171996084"},
{file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61f1f08adfaa9cc02e0cbc94f478140385cbd52d5b3c5a657c2fceb15de8d1fb"},
{file = "pydantic-1.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:32937835e525d92c98a1512218db4eed9ddc8f4ee2a78382d77f54341972c0e7"},
{file = "pydantic-1.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd5c531b22928e63d0cb1868dee76123456e1de2f1cb45879e9e7a3f3f1779b"},
{file = "pydantic-1.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e277bd18339177daa62a294256869bbe84df1fb592be2716ec62627bb8d7c81d"},
{file = "pydantic-1.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f15277d720aa57e173954d237628a8d304896364b9de745dcb722f584812c7"},
{file = "pydantic-1.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b243b564cea2576725e77aeeda54e3e0229a168bc587d536cd69941e6797543d"},
{file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3ce13a558b484c9ae48a6a7c184b1ba0e5588c5525482681db418268e5f86186"},
{file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ac1cd4deed871dfe0c5f63721e29debf03e2deefa41b3ed5eb5f5df287c7b70"},
{file = "pydantic-1.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:b1eb6610330a1dfba9ce142ada792f26bbef1255b75f538196a39e9e90388bf4"},
{file = "pydantic-1.10.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4ca83739c1263a044ec8b79df4eefc34bbac87191f0a513d00dd47d46e307a65"},
{file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea4e2a7cb409951988e79a469f609bba998a576e6d7b9791ae5d1e0619e1c0f2"},
{file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53de12b4608290992a943801d7756f18a37b7aee284b9ffa794ee8ea8153f8e2"},
{file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:60184e80aac3b56933c71c48d6181e630b0fbc61ae455a63322a66a23c14731a"},
{file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:415a3f719ce518e95a92effc7ee30118a25c3d032455d13e121e3840985f2efd"},
{file = "pydantic-1.10.6-cp37-cp37m-win_amd64.whl", hash = "sha256:72cb30894a34d3a7ab6d959b45a70abac8a2a93b6480fc5a7bfbd9c935bdc4fb"},
{file = "pydantic-1.10.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3091d2eaeda25391405e36c2fc2ed102b48bac4b384d42b2267310abae350ca6"},
{file = "pydantic-1.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:751f008cd2afe812a781fd6aa2fb66c620ca2e1a13b6a2152b1ad51553cb4b77"},
{file = "pydantic-1.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12e837fd320dd30bd625be1b101e3b62edc096a49835392dcf418f1a5ac2b832"},
{file = "pydantic-1.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d92831d0115874d766b1f5fddcdde0c5b6c60f8c6111a394078ec227fca6d"},
{file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:476f6674303ae7965730a382a8e8d7fae18b8004b7b69a56c3d8fa93968aa21c"},
{file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a2be0a0f32c83265fd71a45027201e1278beaa82ea88ea5b345eea6afa9ac7f"},
{file = "pydantic-1.10.6-cp38-cp38-win_amd64.whl", hash = "sha256:0abd9c60eee6201b853b6c4be104edfba4f8f6c5f3623f8e1dba90634d63eb35"},
{file = "pydantic-1.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6195ca908045054dd2d57eb9c39a5fe86409968b8040de8c2240186da0769da7"},
{file = "pydantic-1.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43cdeca8d30de9a897440e3fb8866f827c4c31f6c73838e3a01a14b03b067b1d"},
{file = "pydantic-1.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c19eb5163167489cb1e0161ae9220dadd4fc609a42649e7e84a8fa8fff7a80f"},
{file = "pydantic-1.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62"},
{file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:528dcf7ec49fb5a84bf6fe346c1cc3c55b0e7603c2123881996ca3ad79db5bfc"},
{file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:163e79386c3547c49366e959d01e37fc30252285a70619ffc1b10ede4758250a"},
{file = "pydantic-1.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:189318051c3d57821f7233ecc94708767dd67687a614a4e8f92b4a020d4ffd06"},
{file = "pydantic-1.10.6-py3-none-any.whl", hash = "sha256:acc6783751ac9c9bc4680379edd6d286468a1dc8d7d9906cd6f1186ed682b2b0"},
{file = "pydantic-1.10.6.tar.gz", hash = "sha256:cf95adb0d1671fc38d8c43dd921ad5814a735e7d9b4d9e437c088002863854fd"},
]
[package.dependencies]
typing-extensions = ">=4.2.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
[[package]]
name = "pytest"
version = "7.2.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"},
{file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"},
]
[package.dependencies]
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "termcolor"
version = "2.2.0"
@@ -148,7 +305,7 @@ tests = ["pytest", "pytest-cov"]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -160,7 +317,7 @@ files = [
name = "typing-extensions"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -171,4 +328,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "95dd14e87750e4830d773cac5ea2f5b810d406fb34b1913243c8ba6a2145db54"
content-hash = "6482f7b9919f0671ca3ece7b69a1783b8b7284cc54f6b0e1b390613806965f7b"
+7 -9
View File
@@ -1,6 +1,6 @@
__all__ = (
"CommandError",
"Command",
"CommandExecutor",
)
@@ -8,7 +8,7 @@ import logging
import subprocess
from pathlib import Path
logger = logging.getLogger(">")
logger = logging.getLogger("command")
class CommandError(RuntimeError):
@@ -17,18 +17,16 @@ class CommandError(RuntimeError):
return self.args[0].decode("utf-8").strip()
class Command:
class CommandExecutor:
_logger = logger
def __init__(self, executor: str, *args: str) -> None:
self._executor = executor
self._args = args
def __init__(self, command: str) -> None:
self._command = command
def run(self, basepath: Path):
args = [self._executor, *self._args]
self._logger.debug(" ".join(args))
self._logger.debug(self._command)
result = subprocess.run(
args,
self._command.split(" "),
cwd=basepath,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
+35
View File
@@ -0,0 +1,35 @@
[presets.python]
filename = "pyproject.toml"
loader = "tomli:load"
[presets.python.tasks.setup.poetry]
conditions = { "build-system.build-backend" = "poetry.core.masonry.api" }
commands = ["poetry env use 3.8", "poetry install"]
[presets.python.tasks.test.pytest]
conditions = { "tool.poetry.group.dev.dependencies.pytest" = "*" }
commands = ["poetry run pytest -p no:warnings"]
[presets.python.tasks.lint.black]
conditions = { "tool.poetry.group.dev.dependencies.black" = "*" }
commands = ["poetry run black --check --diff ./"]
[presets.python.tasks.format.black]
conditions = { "tool.poetry.group.dev.dependencies.black" = "*" }
commands = ["poetry run black ./"]
[presets.python.tasks.lint.isort]
conditions = { "tool.poetry.group.dev.dependencies.isort" = "*" }
commands = ["poetry run isort --profile black --check-only ./"]
[presets.python.tasks.format.isort]
conditions = { "tool.poetry.group.dev.dependencies.isort" = "*" }
commands = ["poetry run isort ./"]
[presets.nodejs]
filename = "package.json"
loader = "json:load"
[presets.nodejs.tasks.setup.npm]
conditions = {}
commands = ["npm install-clean"]
-24
View File
@@ -1,24 +0,0 @@
from pathlib import Path
from typing import Optional
from ..entry import Entry
class NodejsEntry(Entry):
TYPE: str = "nodejs"
COMMANDS = {
"setup": [
["npm", "install-clean"],
],
"test": [
["npm", "test"],
],
}
def __init__(self, basepath: Path):
super().__init__(self.TYPE, self.COMMANDS, basepath)
@classmethod
def detect(cls, basepath: Path) -> Optional["NodejsEntry"]:
if (basepath / "package.json").exists():
return cls(basepath)
-33
View File
@@ -1,33 +0,0 @@
from pathlib import Path
from typing import Optional
from ..entry import Entry
class PythonEntry(Entry):
TYPE: str = "python"
COMMANDS = {
"setup": [
["poetry", "env", "use", "3.8"],
["poetry", "install"],
],
"lint": [
["poetry", "run", "black", "--check", "--diff", "./"],
["poetry", "run", "isort", "--profile", "black", "--check-only", "./"],
],
"format": [
["poetry", "run", "isort", "./"],
["poetry", "run", "black", "./"],
],
"test": [
["poetry", "run", "pytest", "-p", "no:warnings"],
],
}
def __init__(self, basepath: Path):
super().__init__(self.TYPE, self.COMMANDS, basepath)
@classmethod
def detect(cls, basepath: Path) -> Optional["PythonEntry"]:
if (basepath / "pyproject.toml").exists():
return cls(basepath)
+48 -24
View File
@@ -1,19 +1,20 @@
__all__ = (
"EntryCommandNotImplementedError",
"Entry",
)
import logging
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, Iterable, List, Optional
from .command import Command
from .command import CommandExecutor
__all__ = (
"EntryTaskNotImplementedError",
"Entry",
"EntryDetector",
"EntryResolver",
)
logger = logging.getLogger("entry")
class EntryCommandNotImplementedError(NotImplementedError):
class EntryTaskNotImplementedError(NotImplementedError):
pass
@@ -22,27 +23,50 @@ class Entry:
def __init__(
self,
entry_type: str,
commands: Dict[str, List[List[str]]],
tasks: Dict[str, List[str]],
basepath: Path,
):
self._type = entry_type
self._commands = commands
self._tasks = tasks
self._basepath = basepath
def run_command(self, command_name: str):
if command_name == "show":
self._logger.info(f"{self}")
def show(self):
self._logger.info(f"{self}")
for task_name, commands in self._tasks.items():
self._logger.debug(f" {task_name}")
for command in commands:
self._logger.trace(f" {command}")
def run_task(self, task_name: str):
if task_name == "show":
self.show()
return
self._logger.info(f"{command_name} {self}")
if command_name not in self._commands:
raise EntryCommandNotImplementedError(command_name)
for command in self._commands[command_name]:
Command(*command).run(self._basepath)
if task_name not in self._tasks:
raise EntryTaskNotImplementedError(task_name)
self._logger.info(f"{task_name} {self}")
for command in self._tasks[task_name]:
CommandExecutor(command).run(self._basepath)
def __repr__(self):
return f"{self._type} {str(self._basepath)}"
return f"{str(self._basepath)}"
@classmethod
def detect(cls, basepath: Path) -> Optional["Entry"]:
class EntryDetector:
def detect_entry(self, basepath: Path) -> Optional["Entry"]:
raise NotImplemented
class EntryResolver:
def __init__(self, detector: EntryDetector, ignore: List[str]):
self._detector = detector
self._ignore = ignore
def resolve(self, basepath: Path, depth: int = 128) -> Iterable[Entry]:
if basepath.is_dir():
if basepath.name in self._ignore:
return
entry = self._detector.detect_entry(basepath)
if entry:
yield entry
if depth > 0 and basepath.is_dir():
for path in basepath.iterdir():
yield from self.resolve(path, depth - 1)
+1 -1
View File
@@ -76,6 +76,6 @@ class CustomFormatter(logging.Formatter):
}
def format(self, record: logging.LogRecord):
log_fmt = colored(record.name, "green") + " " + self.FORMATS.get(record.levelno)
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
+1 -2
View File
@@ -2,7 +2,6 @@ import logging
import os
import sys
from argparse import ArgumentParser
from ast import arg
from pathlib import Path
from project_manager.logging import CustomFormatter
@@ -43,7 +42,7 @@ def run():
basepath = Path(args.path)
manager = ProjectManager(basepath)
result = manager.run_command(args.task)
result = manager.run_task(args.task)
if result["failed"] > 0:
sys.exit(1)
+8 -9
View File
@@ -4,10 +4,8 @@ from pathlib import Path
from typing import Dict, Iterable
from .command import CommandError
from .entries.nodejs import NodejsEntry
from .entries.python import PythonEntry
from .entry import Entry, EntryCommandNotImplementedError
from .resolver import EntryDetector, EntryResolver
from .entry import Entry, EntryResolver, EntryTaskNotImplementedError
from .preset.entry import PresetEntryDetector
__all__ = ("ProjectManager",)
@@ -20,20 +18,21 @@ class ProjectManager:
def __init__(self, basepath: Path):
self._basepaht = basepath
self._resolver = EntryResolver(
EntryDetector(PythonEntry, NodejsEntry), ["node_modules", ".venv", ".git"]
PresetEntryDetector(Path(__file__).parent / "default_preset.toml"),
["node_modules", ".venv", ".git"],
)
def _iter_entries(self) -> Iterable[Entry]:
return self._resolver.resolve(self._basepaht)
def run_command(self, command_name: str) -> Dict[str, int]:
def run_task(self, task_name: str) -> Dict[str, int]:
counter = Counter()
for entry in self._iter_entries():
try:
entry.run_command(command_name)
entry.run_task(task_name)
counter["success"] += 1
except EntryCommandNotImplementedError:
self._logger.warn(f"{command_name} {entry}")
except EntryTaskNotImplementedError:
self._logger.warn(f"{task_name} {entry}")
counter["skipped"] += 1
except CommandError as error:
self._logger.error(f"{entry}\n{error.output}")
+28
View File
@@ -0,0 +1,28 @@
from collections import defaultdict
from pathlib import Path
from typing import Optional
from ..entry import Entry, EntryDetector
from .util import PresetUtil
__all__ = ("PresetEntryDetector",)
class PresetEntryDetector(EntryDetector):
def __init__(self, filename: Path):
self._preset = PresetUtil.resolve_preset(filename)
def detect_entry(self, basepath: Path) -> Optional["Entry"]:
tasks = defaultdict(list)
for preset_name, preset in self._preset.presets.items():
filename = basepath / preset.filename
if filename.exists():
loader = PresetUtil.resolve_loader(preset.loader)
with open(filename, "rb") as fd:
data = loader(fd)
for task_name, task_pack in preset.tasks.items():
for task_item_name, task in task_pack.items():
if PresetUtil.check_conditions(data, task.conditions):
tasks[task_name] += task.commands
if tasks:
return Entry(tasks, basepath)
+20
View File
@@ -0,0 +1,20 @@
from typing import Dict, List
from pydantic import BaseModel
__all__ = ("TaskPreset", "FilePreset", "Preset")
class TaskPreset(BaseModel):
conditions: Dict[str, str]
commands: List[str]
class FilePreset(BaseModel):
filename: str
loader: str
tasks: Dict[str, Dict[str, TaskPreset]]
class Preset(BaseModel):
presets: Dict[str, FilePreset]
+45
View File
@@ -0,0 +1,45 @@
__all__ = ("PresetUtil",)
import importlib
from pathlib import Path
from typing import Any, Dict
import tomli
from .model import Preset
class PresetUtil:
@classmethod
def resolve_loader(cls, loader: str) -> Any:
module_name, item_name = loader.split(":")
module = importlib.import_module(module_name)
return getattr(module, item_name)
@classmethod
def get_key_path_value(cls, key_path: str, data: dict):
path = key_path.split(".")
value = data.get(path[0])
for key in path[1:]:
if value:
value = value.get(key)
return value
@classmethod
def match_condition_value(cls, value: Any, pattern: str) -> bool:
if pattern == "*":
return bool(value)
return value == pattern
@classmethod
def check_conditions(cls, data: dict, conditions: Dict[str, str]) -> bool:
for key, value in conditions.items():
if not cls.match_condition_value(cls.get_key_path_value(key, data), value):
return False
return True
@classmethod
def resolve_preset(cls, filename: Path) -> Preset:
with open(filename, "rb") as fd:
preset_data = tomli.load(fd)
return Preset(**preset_data)
-37
View File
@@ -1,37 +0,0 @@
from pathlib import Path
from typing import Iterable, List, Optional, Type
from .entry import Entry
__all__ = (
"EntryDetector",
"EntryResolver",
)
class EntryDetector:
def __init__(self, *entry_types: Type[Entry]):
self._entry_types = entry_types
def detect(self, basepath: Path) -> Optional[Entry]:
for entry_type in self._entry_types:
entry = entry_type.detect(basepath)
if entry:
return entry
class EntryResolver:
def __init__(self, detector: EntryDetector, ignore: List[str]):
self._detector = detector
self._ignore = ignore
def resolve(self, basepath: Path, depth: int = 128) -> Iterable[Entry]:
if basepath.is_dir():
if basepath.name in self._ignore:
return
entry = self._detector.detect(basepath)
if entry:
yield entry
if depth > 0 and basepath.is_dir():
for path in basepath.iterdir():
yield from self.resolve(path, depth - 1)
+4
View File
@@ -9,9 +9,13 @@ packages = [{ include = "project_manager" }]
[tool.poetry.dependencies]
python = "^3.8"
termcolor = "^2.2.0"
pydantic = "^1.10.6"
tomli = "^2.0.1"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"
pytest = "^7.2.2"
isort = "^5.12.0"
[tool.poetry.scripts]
pm = "project_manager.main:run"