feat: rework default entries

This commit is contained in:
2023-03-15 10:26:33 +03:00
parent 9345b27aef
commit 23ca8c0cac
14 changed files with 101 additions and 116 deletions
+14 -8
View File
@@ -1,4 +1,7 @@
__all__ = ("Command",) __all__ = (
"CommandError",
"Command",
)
import logging import logging
@@ -8,6 +11,12 @@ from pathlib import Path
logger = logging.getLogger(">") logger = logging.getLogger(">")
class CommandError(RuntimeError):
@property
def output(self):
return self.args[0].decode("utf-8").strip()
class Command: class Command:
_logger = logger _logger = logger
@@ -25,15 +34,12 @@ class Command:
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) )
self._logstd(result.stdout, logging.TRACE) self._logstd(result.stdout, logging.TRACE)
self._logstd(result.stderr, logging.TRACE)
if result.returncode != 0: if result.returncode != 0:
self._logstd(result.stderr, logging.ERROR) raise CommandError(result.stderr)
raise RuntimeError(result.stderr)
else:
self._logstd(result.stderr, logging.WARNING)
@classmethod @classmethod
def _logstd(cls, data: bytes, level: int): def _logstd(cls, data: bytes, level: int):
if data: if data:
for line in data.decode("utf-8").strip().splitlines(): output = data.decode("utf-8").strip()
if line: cls._logger.log(level, f"\n{output}\n")
cls._logger.log(level, line)
@@ -1,20 +1,22 @@
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from project_manager.nodejs.command import NpmCommand
from ..entry import Entry from ..entry import Entry
class NodejsEntry(Entry): class NodejsEntry(Entry):
TYPE: str = "nodejs" TYPE: str = "nodejs"
COMMANDS = {
"setup": [
["npm", "install-clean"],
],
"test": [
["npm", "test"],
],
}
def __init__(self, basepath: Path): def __init__(self, basepath: Path):
super().__init__(self.TYPE, basepath) super().__init__(self.TYPE, self.COMMANDS, basepath)
def setup(self):
self._logger.info(f"setup {self}")
NpmCommand.install_clean().run(self._basepath)
@classmethod @classmethod
def detect(cls, basepath: Path) -> Optional["NodejsEntry"]: def detect(cls, basepath: Path) -> Optional["NodejsEntry"]:
+33
View File
@@ -0,0 +1,33 @@
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)
+18 -15
View File
@@ -6,7 +6,9 @@ __all__ = (
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Dict, List, Optional
from .command import Command
logger = logging.getLogger("entry") logger = logging.getLogger("entry")
@@ -18,24 +20,25 @@ class EntryCommandNotImplementedError(NotImplementedError):
class Entry: class Entry:
_logger = logger _logger = logger
def __init__(self, entry_type: str, basepath: Path): def __init__(
self,
entry_type: str,
commands: Dict[str, List[List[str]]],
basepath: Path,
):
self._type = entry_type self._type = entry_type
self._commands = commands
self._basepath = basepath self._basepath = basepath
def setup(self): def run_command(self, command_name: str):
raise EntryCommandNotImplementedError("setup") if command_name == "show":
def lint(self):
raise EntryCommandNotImplementedError("lint")
def format(self):
raise EntryCommandNotImplementedError("format")
def test(self):
raise EntryCommandNotImplementedError("test")
def show(self):
self._logger.info(f"{self}") self._logger.info(f"{self}")
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)
def __repr__(self): def __repr__(self):
return f"{self._type} {str(self._basepath)}" return f"{self._type} {str(self._basepath)}"
+2 -2
View File
@@ -67,8 +67,8 @@ class CustomFormatter(logging.Formatter):
super().__init__() super().__init__()
self.fmt = fmt self.fmt = fmt
self.FORMATS = { self.FORMATS = {
logging.TRACE: colored(self.fmt, "dark_grey"), logging.TRACE: colored(self.fmt),
logging.DEBUG: colored(self.fmt, "grey"), logging.DEBUG: colored(self.fmt, "white"),
logging.INFO: colored(self.fmt, "blue"), logging.INFO: colored(self.fmt, "blue"),
logging.WARNING: colored(self.fmt, "yellow"), logging.WARNING: colored(self.fmt, "yellow"),
logging.ERROR: colored(self.fmt, "red"), logging.ERROR: colored(self.fmt, "red"),
+4 -4
View File
@@ -1,5 +1,6 @@
import logging import logging
import os import os
import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from ast import arg from ast import arg
from pathlib import Path from pathlib import Path
@@ -31,9 +32,6 @@ def run():
if args.quiet: if args.quiet:
logging_level = logging.CRITICAL logging_level = logging.CRITICAL
if logging_level <= logging.INFO:
logging.getLogger(">").setLevel(logging.CRITICAL)
stdout_handler = logging.StreamHandler() stdout_handler = logging.StreamHandler()
stdout_handler.setFormatter(CustomFormatter("%(message)s")) stdout_handler.setFormatter(CustomFormatter("%(message)s"))
@@ -45,7 +43,9 @@ def run():
basepath = Path(args.path) basepath = Path(args.path)
manager = ProjectManager(basepath) manager = ProjectManager(basepath)
manager.run_command(args.task) result = manager.run_command(args.task)
if result["failed"] > 0:
sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
+12 -6
View File
@@ -3,9 +3,10 @@ from collections import Counter
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable 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 .entry import Entry, EntryCommandNotImplementedError
from .nodejs.entry import NodejsEntry
from .python.entry import PythonEntry
from .resolver import EntryDetector, EntryResolver from .resolver import EntryDetector, EntryResolver
__all__ = ("ProjectManager",) __all__ = ("ProjectManager",)
@@ -18,20 +19,25 @@ class ProjectManager:
def __init__(self, basepath: Path): def __init__(self, basepath: Path):
self._basepaht = basepath self._basepaht = basepath
self._resolver = EntryResolver(EntryDetector(PythonEntry, NodejsEntry)) self._resolver = EntryResolver(
EntryDetector(PythonEntry, NodejsEntry), ["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: str) -> Dict[str, int]: def run_command(self, command_name: str) -> Dict[str, int]:
counter = Counter() counter = Counter()
for entry in self._iter_entries(): for entry in self._iter_entries():
try: try:
getattr(entry, command)() entry.run_command(command_name)
counter["success"] += 1 counter["success"] += 1
except EntryCommandNotImplementedError: except EntryCommandNotImplementedError:
self._logger.warn(f"{command} {entry}") self._logger.warn(f"{command_name} {entry}")
counter["skipped"] += 1 counter["skipped"] += 1
except CommandError as error:
self._logger.error(f"{entry}\n{error.output}")
counter["failed"] += 1
except Exception: except Exception:
self._logger.exception(entry) self._logger.exception(entry)
counter["failed"] += 1 counter["failed"] += 1
-18
View File
@@ -1,18 +0,0 @@
from ..command import Command
__all__ = ("NpmCommand",)
class NpmCommand(Command):
EXECUTOR: str = "npm"
def __init__(self, *args: str) -> None:
super().__init__(self.EXECUTOR, *args)
@classmethod
def install(cls) -> "NpmCommand":
return cls("install")
@classmethod
def install_clean(cls) -> "NpmCommand":
return cls("install-clean")
View File
-18
View File
@@ -1,18 +0,0 @@
from ..command import Command
__all__ = ("PoetryCommand",)
class PoetryCommand(Command):
EXECUTOR: str = "poetry"
def __init__(self, *args: str) -> None:
super().__init__(self.EXECUTOR, *args)
@classmethod
def env_use(cls, python_version: str) -> "PoetryCommand":
return cls("env", "use", python_version)
@classmethod
def install(cls) -> "PoetryCommand":
return cls("install")
-34
View File
@@ -1,34 +0,0 @@
from pathlib import Path
from typing import Optional
from ..entry import Entry
from .command import PoetryCommand
class PythonEntry(Entry):
TYPE: str = "python"
def __init__(self, basepath: Path):
super().__init__(self.TYPE, basepath)
def setup(self):
self._logger.info(f"setup {self}")
PoetryCommand.env_use("3.8").run(self._basepath)
PoetryCommand.install().run(self._basepath)
def lint(self):
self._logger.info(f"lint {self}")
PoetryCommand("run", "black", "--check", "--diff", "./").run(self._basepath)
PoetryCommand("run", "isort", "--profile", "black", "--check-only", "./").run(
self._basepath
)
def format(self):
self._logger.info(f"format {self}")
PoetryCommand("run", "isort", "./").run(self._basepath)
PoetryCommand("run", "black", "./").run(self._basepath)
@classmethod
def detect(cls, basepath: Path) -> Optional["PythonEntry"]:
if (basepath / "pyproject.toml").exists():
return cls(basepath)
+6 -3
View File
@@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Iterable, Optional, Type from typing import Iterable, List, Optional, Type
from .entry import Entry from .entry import Entry
@@ -21,11 +21,14 @@ class EntryDetector:
class EntryResolver: class EntryResolver:
def __init__(self, detector: EntryDetector): def __init__(self, detector: EntryDetector, ignore: List[str]):
self._detector = detector self._detector = detector
self._ignore = ignore
def resolve(self, basepath: Path, depth: int = 2) -> Iterable[Entry]: def resolve(self, basepath: Path, depth: int = 128) -> Iterable[Entry]:
if basepath.is_dir(): if basepath.is_dir():
if basepath.name in self._ignore:
return
entry = self._detector.detect(basepath) entry = self._detector.detect(basepath)
if entry: if entry:
yield entry yield entry
+2
View File
@@ -0,0 +1,2 @@
def test_entry():
pass