projectsyn/commodore

View on GitHub
commodore/component/template.py

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
from __future__ import annotations

from pathlib import Path
from shutil import rmtree

import click
import git
import yaml

from commodore.config import Config
from commodore.component import Component, component_dir
from commodore.dependency_templater import Templater
from commodore.multi_dependency import MultiDependency


class ComponentTemplater(Templater):
    library: bool
    post_process: bool
    _matrix_tests: bool

    @classmethod
    def from_existing(cls, config: Config, path: Path):
        return cls._base_from_existing(config, path, "component")

    @property
    def _has_lib(self) -> bool:
        """Determine whether component has a component library by checking the presence
        of the `lib` folder."""
        return (self.target_dir / "lib").is_dir()

    @property
    def _has_pp(self) -> bool:
        """Determine whether component has postprocessing filters by looking at the
        component class contents."""
        with open(
            self.target_dir / "class" / f"{self.slug}.yml", "r", encoding="utf-8"
        ) as cls:
            class_data = yaml.safe_load(cls)
            return "postprocess" in class_data["parameters"].get("commodore", {})

    def _initialize_from_cookiecutter_args(self, cookiecutter_args: dict[str, str]):
        update_cruft_json = super()._initialize_from_cookiecutter_args(
            cookiecutter_args
        )

        if "add_lib" not in cookiecutter_args:
            # If `add_lib` is not present in the cookiecutter args, determine if the
            # component has a component library and set the arg in `cookiecutter_args`
            # accordingly.
            cookiecutter_args["add_lib"] = "y" if self._has_lib else "n"
            update_cruft_json = True

        if "add_pp" not in cookiecutter_args:
            # If `add_pp` is not present in the cookiecutter args, determine if the
            # component has postprocessing filters and set the arg in
            # `cookiecutter_args` accordingly.
            cookiecutter_args["add_pp"] = "y" if self._has_pp else "n"
            update_cruft_json = True

        if (self.target_dir / ".sync.yml").is_file():
            # Migrate copyright information from modulesync config, if it's present and
            # the information is missing in the cookiecutter args.
            with open(self.target_dir / ".sync.yml", "r", encoding="utf-8") as f:
                sync_yml = yaml.safe_load(f)
            license_data = sync_yml.get("LICENSE", {})
            if "copyright_holder" not in cookiecutter_args:
                cookiecutter_args["copyright_holder"] = license_data.get(
                    "holder", "VSHN AG <info@vshn.ch>"
                )
                update_cruft_json = True
            self.copyright_holder = cookiecutter_args["copyright_holder"]
            if "copyright_year" not in cookiecutter_args:
                cookiecutter_args["copyright_year"] = str(
                    license_data.get("year", 2021)
                )
                update_cruft_json = True
            self.copyright_year = cookiecutter_args["copyright_year"]

        self.library = cookiecutter_args["add_lib"] == "y"
        self.post_process = cookiecutter_args["add_pp"] == "y"
        self.matrix_tests = cookiecutter_args["add_matrix"] == "y"

        return update_cruft_json

    @property
    def cookiecutter_args(self) -> dict[str, str]:
        args = super().cookiecutter_args
        args["add_lib"] = "y" if self.library else "n"
        args["add_pp"] = "y" if self.post_process else "n"
        args["add_matrix"] = "y" if self.matrix_tests else "n"
        return args

    @property
    def matrix_tests(self) -> bool:
        if len(self.test_cases) > 1:
            if not self._matrix_tests:
                click.echo(" > Forcing matrix tests when multiple test cases requested")
            return True
        return self._matrix_tests

    @matrix_tests.setter
    def matrix_tests(self, matrix_tests: bool) -> None:
        self._matrix_tests = matrix_tests

    @property
    def deptype(self) -> str:
        return "component"

    def dependency_dir(self) -> Path:
        return component_dir(self.config.work_dir, self.slug)

    def delete(self):
        cdir = component_dir(self.config.work_dir, self.slug)
        if cdir.exists():
            cr = git.Repo(cdir)
            cdep = MultiDependency(
                cr.remote().url, self.config.inventory.dependencies_dir
            )
            component = Component(
                self.slug, dependency=cdep, work_dir=self.config.work_dir
            )

            if not self.config.force:
                click.confirm(
                    "Are you sure you want to delete component "
                    f"{self.slug}? This action cannot be undone",
                    abort=True,
                )
            rmtree(component.target_directory)
            # We check for other checkouts here, because our MultiDependency doesn't
            # know if there's other dependencies which would be registered on it.
            if not cdep.has_checkouts():
                # Also delete bare copy of component repo, if there's no other
                # worktree checkouts for the same dependency repo.
                rmtree(cdep.repo_directory)
            else:
                click.echo(
                    f" > Not deleting bare copy of repository {cdep.url}. "
                    + "Other worktrees refer to the same reposiotry."
                )

            click.secho(f"Component {self.slug} successfully deleted 🎉", bold=True)
        else:
            raise click.BadParameter(
                "Cannot find component with slug " f"'{self.slug}'."
            )