feat: update presets

This commit is contained in:
2023-03-19 21:21:48 +03:00
parent 8b718fda79
commit 81bf685fec
6 changed files with 150 additions and 59 deletions
+35 -16
View File
@@ -1,35 +1,54 @@
[presets.python] [presets.python]
filename = "pyproject.toml" filename = "pyproject.toml"
loader = "tomli:load"
[presets.python.tasks.setup.poetry] [presets.python.tasks.setup.poetry]
conditions = { "build-system.build-backend" = "poetry.core.masonry.api" } conditions = [
commands = ["poetry env use 3.8", "poetry install"] "file(pyproject.toml) | get(build-system.build-backend) | equal(poetry.core.masonry.api)",
]
variables.python_version = "file(pyproject.toml) | get(tool.poetry.dependencies.python) | search((\\d.\\d))"
commands = ["poetry env use {python_version}", "poetry install"]
[presets.python.tasks.test.pytest] [presets.python.tasks.test.pytest]
conditions = { "tool.poetry.group.dev.dependencies.pytest" = "*" } conditions = [
"file(pyproject.toml) | get(tool.poetry.group.dev.dependencies.pytest)",
]
commands = ["poetry run pytest -p no:warnings"] 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] [presets.python.tasks.lint.isort]
conditions = { "tool.poetry.group.dev.dependencies.isort" = "*" } conditions = [
"file(pyproject.toml) | get(tool.poetry.group.dev.dependencies.isort)",
]
commands = ["poetry run isort --profile black --check-only ./"] commands = ["poetry run isort --profile black --check-only ./"]
[presets.python.tasks.format.isort] [presets.python.tasks.format.isort]
conditions = { "tool.poetry.group.dev.dependencies.isort" = "*" } conditions = [
"file(pyproject.toml) | get(tool.poetry.group.dev.dependencies.isort)",
]
commands = ["poetry run isort ./"] commands = ["poetry run isort ./"]
[presets.python.tasks.lint.black]
conditions = [
"file(pyproject.toml) | get(tool.poetry.group.dev.dependencies.black)",
]
commands = ["poetry run black --check --diff ./"]
[presets.python.tasks.format.black]
conditions = [
"file(pyproject.toml) | get(tool.poetry.group.dev.dependencies.black)",
]
commands = ["poetry run black ./"]
[presets.nodejs] [presets.nodejs]
filename = "package.json" filename = "package.json"
loader = "json:load"
[presets.nodejs.tasks.setup.npm] [presets.nodejs.tasks.setup.npm]
conditions = {} conditions = ["file(package-lock.json)"]
commands = ["npm install-clean"] commands = ["npm install-clean"]
[presets.nodejs.tasks.setup.npm-legacy]
conditions = ["file(package-lock.json) | not"]
commands = ["npm install"]
[presets.nodejs.tasks.test.jest]
conditions = ["file(package.json) | get(devDependencies.jest)"]
commands = ["npx jest"]
+4 -2
View File
@@ -13,13 +13,15 @@ logger = logging.getLogger("project")
class ProjectManager: class ProjectManager:
DEFAULT_PRESET = Path(__file__).parent / "default_preset.toml"
DEFAULT_IGNORE = ["node_modules", ".venv", ".git"]
_logger = logger _logger = logger
def __init__(self, basepath: Path): def __init__(self, basepath: Path):
self._basepaht = basepath self._basepaht = basepath
self._resolver = EntryResolver( self._resolver = EntryResolver(
PresetEntryDetector(Path(__file__).parent / "default_preset.toml"), PresetEntryDetector(self.DEFAULT_PRESET),
["node_modules", ".venv", ".git"], self.DEFAULT_IGNORE,
) )
def _iter_entries(self) -> Iterable[Entry]: def _iter_entries(self) -> Iterable[Entry]:
+14 -5
View File
@@ -4,6 +4,7 @@ from typing import Optional
from ..entry import Entry, EntryDetector from ..entry import Entry, EntryDetector
from .util import PresetUtil from .util import PresetUtil
from .value import ValueResolver
__all__ = ("PresetEntryDetector",) __all__ = ("PresetEntryDetector",)
@@ -17,12 +18,20 @@ class PresetEntryDetector(EntryDetector):
for preset_name, preset in self._preset.presets.items(): for preset_name, preset in self._preset.presets.items():
filename = basepath / preset.filename filename = basepath / preset.filename
if filename.exists(): if filename.exists():
loader = PresetUtil.resolve_loader(preset.loader) value_resolver = ValueResolver(basepath)
with open(filename, "rb") as fd:
data = loader(fd)
for task_name, task_pack in preset.tasks.items(): for task_name, task_pack in preset.tasks.items():
for task_item_name, task in task_pack.items(): for task_item_name, task in task_pack.items():
if PresetUtil.check_conditions(data, task.conditions): if value_resolver.check_conditions(task.conditions):
tasks[task_name] += task.commands variables = (
{
k: value_resolver.resolve_value(v)
for k, v in task.variables.items()
}
if task.variables
else {}
)
tasks[task_name] += [
command.format(**variables) for command in task.commands
]
if tasks: if tasks:
return Entry(tasks, basepath) return Entry(tasks, basepath)
+7 -4
View File
@@ -1,18 +1,21 @@
from typing import Dict, List from typing import Dict, List, Optional
from pydantic import BaseModel from pydantic import BaseModel
__all__ = ("TaskPreset", "FilePreset", "Preset") __all__ = ("PipeStr", "TaskPreset", "FilePreset", "Preset")
PipeStr = str
class TaskPreset(BaseModel): class TaskPreset(BaseModel):
conditions: Dict[str, str] conditions: List[PipeStr]
variables: Optional[Dict[str, PipeStr]]
commands: List[str] commands: List[str]
class FilePreset(BaseModel): class FilePreset(BaseModel):
filename: str filename: str
loader: str
tasks: Dict[str, Dict[str, TaskPreset]] tasks: Dict[str, Dict[str, TaskPreset]]
+2 -32
View File
@@ -1,43 +1,13 @@
__all__ = ("PresetUtil",)
import importlib
from pathlib import Path from pathlib import Path
from typing import Any, Dict
import tomli import tomli
from .model import Preset from .model import Preset
__all__ = ("PresetUtil",)
class PresetUtil: 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 @classmethod
def resolve_preset(cls, filename: Path) -> Preset: def resolve_preset(cls, filename: Path) -> Preset:
with open(filename, "rb") as fd: with open(filename, "rb") as fd:
+88
View File
@@ -0,0 +1,88 @@
import importlib
import re
from pathlib import Path
from typing import Any, List, Optional
from .model import PipeStr
__all__ = ("ValueResolver",)
class ValueResolver:
PIPE_PATTERN = re.compile("^(\\w+)\\((.*)\\)")
FILE_LOADERS = {
".toml": "tomli:load",
".json": "json:load",
}
def __init__(self, basepath: Path):
self._basepath = basepath
def _reflect(self, value: str) -> Any:
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 pipe_file(self, pipe, filename: str, loader: Optional[str] = None):
filepath = Path(filename)
if not filepath.is_absolute():
filepath = self._basepath / filepath
if loader is None:
loader = self.FILE_LOADERS.get(Path(filename).suffix)
if not filepath.exists():
return None
with open(filepath, "rb") as fd:
if loader:
loader_fun = self._reflect(loader)
data = loader_fun(fd)
else:
data = fd.read()
return data
def pipe_get(self, pipe, keypath: str):
path = keypath.split(".")
value = pipe.get(path[0])
for key in path[1:]:
if value:
value = value.get(key)
return value
def pipe_search(self, pipe, pattern: str, position_arg: str = "1"):
matched = re.compile(pattern).search(pipe)
position = int(position_arg)
if matched:
return matched.group(position)
def pipe_equal(self, pipe, value: Any):
return pipe == value
def pipe_convert(self, pipe, converter: str):
converter_fun = self._reflect(converter)
return converter_fun(pipe)
def pipe_not(self, pipe):
return not pipe
def resolve_value(self, operator: str) -> Any:
chain = [item.strip() for item in operator.split("|")]
pipe = None
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_func = getattr(self, f"pipe_{pipe_name}")
pipe = pipe_func(pipe, *pipe_args)
return pipe
def check_conditions(self, conditions: List[PipeStr]) -> bool:
for condition in conditions:
if not self.resolve_value(condition):
return False
return True