feat: update presets
This commit is contained in:
@@ -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"]
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user