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. # 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]] [[package]]
name = "black" name = "black"
version = "23.1.0" version = "23.1.0"
@@ -77,6 +96,51 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {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]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" 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)"] 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)"] 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]] [[package]]
name = "termcolor" name = "termcolor"
version = "2.2.0" version = "2.2.0"
@@ -148,7 +305,7 @@ tests = ["pytest", "pytest-cov"]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@@ -160,7 +317,7 @@ files = [
name = "typing-extensions" name = "typing-extensions"
version = "4.5.0" version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+" description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@@ -171,4 +328,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "95dd14e87750e4830d773cac5ea2f5b810d406fb34b1913243c8ba6a2145db54" content-hash = "6482f7b9919f0671ca3ece7b69a1783b8b7284cc54f6b0e1b390613806965f7b"
+7 -9
View File
@@ -1,6 +1,6 @@
__all__ = ( __all__ = (
"CommandError", "CommandError",
"Command", "CommandExecutor",
) )
@@ -8,7 +8,7 @@ import logging
import subprocess import subprocess
from pathlib import Path from pathlib import Path
logger = logging.getLogger(">") logger = logging.getLogger("command")
class CommandError(RuntimeError): class CommandError(RuntimeError):
@@ -17,18 +17,16 @@ class CommandError(RuntimeError):
return self.args[0].decode("utf-8").strip() return self.args[0].decode("utf-8").strip()
class Command: class CommandExecutor:
_logger = logger _logger = logger
def __init__(self, executor: str, *args: str) -> None: def __init__(self, command: str) -> None:
self._executor = executor self._command = command
self._args = args
def run(self, basepath: Path): def run(self, basepath: Path):
args = [self._executor, *self._args] self._logger.debug(self._command)
self._logger.debug(" ".join(args))
result = subprocess.run( result = subprocess.run(
args, self._command.split(" "),
cwd=basepath, cwd=basepath,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=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 import logging
from pathlib import Path 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") logger = logging.getLogger("entry")
class EntryCommandNotImplementedError(NotImplementedError): class EntryTaskNotImplementedError(NotImplementedError):
pass pass
@@ -22,27 +23,50 @@ class Entry:
def __init__( def __init__(
self, self,
entry_type: str, tasks: Dict[str, List[str]],
commands: Dict[str, List[List[str]]],
basepath: Path, basepath: Path,
): ):
self._type = entry_type self._tasks = tasks
self._commands = commands
self._basepath = basepath self._basepath = basepath
def run_command(self, command_name: str): def show(self):
if command_name == "show": self._logger.info(f"{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 return
self._logger.info(f"{command_name} {self}") if task_name not in self._tasks:
if command_name not in self._commands: raise EntryTaskNotImplementedError(task_name)
raise EntryCommandNotImplementedError(command_name) self._logger.info(f"{task_name} {self}")
for command in self._commands[command_name]: for command in self._tasks[task_name]:
Command(*command).run(self._basepath) CommandExecutor(command).run(self._basepath)
def __repr__(self): 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 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): 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) formatter = logging.Formatter(log_fmt)
return formatter.format(record) return formatter.format(record)
+1 -2
View File
@@ -2,7 +2,6 @@ import logging
import os import os
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from ast import arg
from pathlib import Path from pathlib import Path
from project_manager.logging import CustomFormatter from project_manager.logging import CustomFormatter
@@ -43,7 +42,7 @@ def run():
basepath = Path(args.path) basepath = Path(args.path)
manager = ProjectManager(basepath) manager = ProjectManager(basepath)
result = manager.run_command(args.task) result = manager.run_task(args.task)
if result["failed"] > 0: if result["failed"] > 0:
sys.exit(1) sys.exit(1)
+8 -9
View File
@@ -4,10 +4,8 @@ from pathlib import Path
from typing import Dict, Iterable from typing import Dict, Iterable
from .command import CommandError from .command import CommandError
from .entries.nodejs import NodejsEntry from .entry import Entry, EntryResolver, EntryTaskNotImplementedError
from .entries.python import PythonEntry from .preset.entry import PresetEntryDetector
from .entry import Entry, EntryCommandNotImplementedError
from .resolver import EntryDetector, EntryResolver
__all__ = ("ProjectManager",) __all__ = ("ProjectManager",)
@@ -20,20 +18,21 @@ class ProjectManager:
def __init__(self, basepath: Path): def __init__(self, basepath: Path):
self._basepaht = basepath self._basepaht = basepath
self._resolver = EntryResolver( 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]: def _iter_entries(self) -> Iterable[Entry]:
return self._resolver.resolve(self._basepaht) 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() counter = Counter()
for entry in self._iter_entries(): for entry in self._iter_entries():
try: try:
entry.run_command(command_name) entry.run_task(task_name)
counter["success"] += 1 counter["success"] += 1
except EntryCommandNotImplementedError: except EntryTaskNotImplementedError:
self._logger.warn(f"{command_name} {entry}") self._logger.warn(f"{task_name} {entry}")
counter["skipped"] += 1 counter["skipped"] += 1
except CommandError as error: except CommandError as error:
self._logger.error(f"{entry}\n{error.output}") 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] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
termcolor = "^2.2.0" termcolor = "^2.2.0"
pydantic = "^1.10.6"
tomli = "^2.0.1"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"
pytest = "^7.2.2"
isort = "^5.12.0"
[tool.poetry.scripts] [tool.poetry.scripts]
pm = "project_manager.main:run" pm = "project_manager.main:run"