feat: add manager, entry and command
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
__all__ = ("Command",)
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger("command")
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
_logger = logger
|
||||||
|
|
||||||
|
def __init__(self, executor: str, *args: str) -> None:
|
||||||
|
self._executor = executor
|
||||||
|
self._args = args
|
||||||
|
|
||||||
|
def run(self, basepath: Path):
|
||||||
|
args = [self._executor, *self._args]
|
||||||
|
self._logger.info(" ".join(args))
|
||||||
|
result = subprocess.run(
|
||||||
|
args,
|
||||||
|
cwd=basepath,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
self._logstd(result.stdout, logging.DEBUG)
|
||||||
|
if result.returncode != 0:
|
||||||
|
self._logstd(result.stderr, logging.ERROR)
|
||||||
|
raise RuntimeError(result.stderr)
|
||||||
|
else:
|
||||||
|
self._logstd(result.stderr, logging.WARNING)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _logstd(cls, data: bytes, level: int):
|
||||||
|
if data:
|
||||||
|
for line in data.decode("utf-8").strip().splitlines():
|
||||||
|
if line:
|
||||||
|
cls._logger.log(level, line)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
__all__ = ("Entry",)
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
logger = logging.getLogger("entry")
|
||||||
|
|
||||||
|
|
||||||
|
class Entry:
|
||||||
|
_logger = logger
|
||||||
|
|
||||||
|
def __init__(self, entry_type: str, basepath: Path):
|
||||||
|
self._type = entry_type
|
||||||
|
self._basepath = basepath
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self._type} {str(self._basepath)}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def detect(cls, basepath: Path) -> Optional["Entry"]:
|
||||||
|
raise NotImplemented
|
||||||
+33
-1
@@ -1,5 +1,37 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from project_manager.manager import ProjectManager
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
print("project-manager")
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument("task", nargs="?", default="show", choices=("show", "setup"))
|
||||||
|
parser.add_argument("-p", "--path", default=os.getcwd())
|
||||||
|
parser.add_argument("-i", "--ignore-errors", action="store_true")
|
||||||
|
parser.add_argument("-v", "--verbose", action="count", default=1)
|
||||||
|
parser.add_argument("-q", "--quiet", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
logging_level = logging.WARNING
|
||||||
|
if args.verbose > 1:
|
||||||
|
logging_level = logging.DEBUG
|
||||||
|
elif args.verbose > 0:
|
||||||
|
logging_level = logging.INFO
|
||||||
|
if args.quiet:
|
||||||
|
logging_level = logging.CRITICAL
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging_level)
|
||||||
|
|
||||||
|
basepath = Path(args.path)
|
||||||
|
manager = ProjectManager(basepath, args.ignore_errors)
|
||||||
|
|
||||||
|
if args.task == "show":
|
||||||
|
manager.show()
|
||||||
|
elif args.task == "setup":
|
||||||
|
manager.setup()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from .entry import Entry
|
||||||
|
from .nodejs.entry import NodejsEntry
|
||||||
|
from .python.entry import PythonEntry
|
||||||
|
from .resolver import EntryDetector, EntryResolver
|
||||||
|
|
||||||
|
__all__ = ("ProjectManager",)
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectManager:
|
||||||
|
def __init__(self, basepath: Path, ignore_entry_error: bool = False):
|
||||||
|
self._basepaht = basepath
|
||||||
|
self._resolver = EntryResolver(EntryDetector(PythonEntry, NodejsEntry))
|
||||||
|
self._ignore_entry_error = ignore_entry_error
|
||||||
|
|
||||||
|
def _iter_entries(self) -> Iterable[Entry]:
|
||||||
|
return self._resolver.resolve(self._basepaht)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
for entry in self._iter_entries():
|
||||||
|
try:
|
||||||
|
entry.setup()
|
||||||
|
except Exception as exc:
|
||||||
|
if not self._ignore_entry_error:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
for entry in self._iter_entries():
|
||||||
|
print(entry)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from project_manager.nodejs.command import NpmCommand
|
||||||
|
|
||||||
|
from ..entry import Entry
|
||||||
|
|
||||||
|
|
||||||
|
class NodejsEntry(Entry):
|
||||||
|
TYPE: str = "nodejs"
|
||||||
|
|
||||||
|
def __init__(self, basepath: Path):
|
||||||
|
super().__init__(self.TYPE, basepath)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self._logger.info(f"setup:{self}")
|
||||||
|
NpmCommand.install_clean().run(self._basepath)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def detect(cls, basepath: Path) -> Optional["NodejsEntry"]:
|
||||||
|
if (basepath / "package.json").exists():
|
||||||
|
return cls(basepath)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def detect(cls, basepath: Path) -> Optional["PythonEntry"]:
|
||||||
|
if (basepath / "pyproject.toml").exists():
|
||||||
|
return cls(basepath)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable, 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):
|
||||||
|
self._detector = detector
|
||||||
|
|
||||||
|
def resolve(self, basepath: Path, depth: int = 2) -> Iterable[Entry]:
|
||||||
|
if basepath.is_dir():
|
||||||
|
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)
|
||||||
Reference in New Issue
Block a user