Skip to content

Commit

Permalink
feat: add the 'copier check' command
Browse files Browse the repository at this point in the history
Resolves: copier-org#1020
  • Loading branch information
wesselyw committed Mar 8, 2023
1 parent beb174f commit 2f221eb
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 3 deletions.
67 changes: 65 additions & 2 deletions copier/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Command line entrypoint. This module declares the Copier CLI applications.
Basically, there are 3 different commands you can run:
Basically, there are 4 different commands you can run:
- [`copier`][copier.cli.CopierApp], the main app, which is a shortcut for the
`copy` and `update` subapps.
Expand Down Expand Up @@ -40,6 +40,15 @@
copier update
```
- [`copier check`][copier.cli.CopierCheckSubApp] to check if a preexisting
project is using the latest version of its template
!!! example
```sh
copier check
```
Below are the docs of each one of those.
"""

Expand All @@ -54,7 +63,7 @@

from copier.tools import copier_version

from .errors import UserMessageError
from .errors import UserMessageError, SubprojectOutdatedError
from .main import Worker
from .types import AnyByStrDict, OptStr, StrSeq

Expand Down Expand Up @@ -114,6 +123,7 @@ class CopierApp(cli.Application):
"""\
copier [MAIN_SWITCHES] [copy] [SUB_SWITCHES] template_src destination_path
copier [MAIN_SWITCHES] [update] [SUB_SWITCHES] [destination_path]
copier [MAIN_SWITCHES] check [SUB_SWITCHES] [destination_path]
"""
)
VERSION = copier_version()
Expand Down Expand Up @@ -351,6 +361,59 @@ def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
return 0


@CopierApp.subcommand("check")
class CopierCheckSubApp(cli.Application):
"""The `copier check` subcommand.
Use this subcommand to check if an existing subproject is using the
latest version of a template.
Attributes:
exit-code: Set [exit-code][] option.
"""

DESCRIPTION = "Check if a copy is using the latest version of its original template"
DESCRIPTION_MORE = dedent(
"""\
The copy must have a valid answers file which contains info
from the last Copier execution, including the source template
(it must be a key called `_src_path`).
If that file contains also `_commit` and `destination_path` is a git
repository, this command will do its best determine whether a newer
version is available, applying PEP 440 to the template's history.
"""
)

exit_code: cli.SwitchAttr = cli.SwitchAttr(
["-e", "--exit-code"],
int,
default=0,
help=(
"Exit code for the command if a newer version is available, "
"for use in scripts."
),
)

@handle_exceptions
def main(self, destination_path: cli.ExistingDirectory = ".") -> int:
"""Call [run_check][copier.main.Worker.run_check].
Parameters:
destination_path:
Only the destination path is needed to check, because the
`src_path` comes from [the answers file][the-copier-answersyml-file].
The subproject must exist. If not specified, the currently
working directory is used.
"""
try:
self.parent._worker(dst_path=destination_path).run_check()
except SubprojectOutdatedError:
return self.exit_code
return 0


# Add --help-all results to docs
if __doc__:
help_io = StringIO()
Expand Down
4 changes: 4 additions & 0 deletions copier/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class UserMessageError(CopierError):
"""Exit the program giving a message to the user."""


class SubprojectOutdatedError(UserMessageError):
"""An old version of the template is being used."""


class UnsupportedVersionError(UserMessageError):
"""Copier version does not support template version."""

Expand Down
55 changes: 54 additions & 1 deletion copier/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from pydantic.json import pydantic_encoder
from questionary import unsafe_prompt

from .errors import CopierAnswersInterrupt, ExtensionNotFoundError, UserMessageError
from .errors import CopierAnswersInterrupt, ExtensionNotFoundError, UserMessageError, SubprojectOutdatedError
from .subproject import Subproject
from .template import Task, Template
from .tools import Style, TemporaryDirectory, printf
Expand Down Expand Up @@ -687,6 +687,43 @@ def run_update(self) -> None:
)
self._apply_update()

def run_check(self) -> None:
"""Check if a subproject is using the latest version of it's template
See [checking a project][checking-a-project].
"""
# Check all you need is there
if self.subproject.template is None or self.subproject.template.ref is None:
raise UserMessageError(
"Cannot check because cannot obtain old template references "
f"from `{self.subproject.answers_relpath}`."
)
if self.template.commit is None:
raise UserMessageError(
"Checking is only supported in git-tracked templates."
)
if not self.subproject.template.version:
raise UserMessageError(
"Cannot check: version from last update not detected."
)
if not self.template.version:
raise UserMessageError("Cannot check: version from template not detected.")

if not self.quiet:
# TODO Unify printing tools
print(
f"Currently using template version {self.subproject.template.version}, "
f"latest is {self.template.version}.",
file=sys.stderr)

if self.template.version > self.subproject.template.version:
if not self.quiet:
print("New template version available.", file=sys.stderr)
raise SubprojectOutdatedError("")
elif not self.quiet:
# TODO Unify printing tools
print("No newer template version available.", file=sys.stderr)

def _apply_update(self):
# Copy old template into a temporary destination
with TemporaryDirectory(
Expand Down Expand Up @@ -865,6 +902,22 @@ def run_auto(
return run_copy(src_path, dst_path, data, **kwargs)


def run_check(
dst_path: StrOrPath = ".",
data: Optional[AnyByStrDict] = None,
**kwargs,
) -> Worker:
"""Check if a subproject is using the latest versio of its template.
See [Worker][copier.main.Worker] fields to understand this function's args.
"""
if data is not None:
kwargs["data"] = data
with Worker(dst_path=Path(dst_path), **kwargs) as worker:
worker.run_check()
return worker


def _remove_old_files(prefix: Path, cmp: dircmp, rm_common: bool = False):
"""Remove files and directories only found in "old" template.
Expand Down

0 comments on commit 2f221eb

Please sign in to comment.