Skip to content

Commit

Permalink
Merge branch 'main' into main-github
Browse files Browse the repository at this point in the history
  • Loading branch information
jannismain committed Sep 25, 2023
2 parents 25f3e19 + 7c37f09 commit 5917679
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 47 deletions.
1 change: 1 addition & 0 deletions .vscode/terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pypa
pyproject
pytest
sampleproject
secho
sendline
setuptools
SPHINXBUILD
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
prune template
prune */__pycache__
exclude copier.yaml
graft src/init_python_project/template
42 changes: 21 additions & 21 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,30 @@ DOC_EXAMPLES = docs/examples/mkdocs docs/examples/sphinx docs/examples/default d
examples: ## build all published examples
examples: $(PUBLISHED_EXAMPLES)

COPIER_ARGS?=--trust --vcs-ref=HEAD
COPIER_DEFAULT_VALUES=-d "project_name=Sample Project" -d "package_name=sample_project"
build/examples/%: COPIER_DEFAULT_VALUES += --defaults
INIT_PYTHON_PROJECT_ARGS=--project-name="Sample Project"
build/examples/%: EXAMPLE_DIR:=$@
build/examples/github: COPIER_DEFAULT_VALUES+=-d user_name=jannismain -d remote=github -d remote_url=git@github.com:jannismain/python-project-template-example.git
build/examples/gitlab%: COPIER_DEFAULT_VALUES+=-d user_name=mkj
build/examples/gitlab_fhg: COPIER_DEFAULT_VALUES+= -d remote=gitlab-fhg -d remote_url=git@gitlab.cc-asp.fraunhofer.de:mkj/sample-project.git
build/examples/gitlab_iis: COPIER_DEFAULT_VALUES+= -d remote=gitlab-iis -d remote_url=git@git01.iis.fhg.de:mkj/sample-project.git
build/examples/gitlab_iis_sphinx: COPIER_DEFAULT_VALUES+= -d remote=gitlab-iis -d remote_url=git@git01.iis.fhg.de:mkj/sample-project-sphinx.git -d docs=sphinx
build/examples/github: INIT_PYTHON_PROJECT_ARGS+=--user-name=jannismain --remote=github --remote-url=git@github.com:jannismain/python-project-template-example.git
build/examples/gitlab%: INIT_PYTHON_PROJECT_ARGS+=--user-name mkj
build/examples/gitlab_fhg: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-fhg --remote-url=git@gitlab.cc-asp.fraunhofer.de:mkj/sample-project.git
build/examples/gitlab_iis: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-iis --remote-url=git@git01.iis.fhg.de:mkj/sample-project.git
build/examples/gitlab_iis_sphinx: INIT_PYTHON_PROJECT_ARGS+=--remote=gitlab-iis --remote-url=git@git01.iis.fhg.de:mkj/sample-project-sphinx.git --docs=sphinx

$(PUBLISHED_EXAMPLES):
$(PUBLISHED_EXAMPLES): uncopy-template copy-template
@echo "Recreating '$@'..."
@rm -rf "$@" && mkdir -p "$@"
@copier copy ${COPIER_ARGS} ${COPIER_DEFAULT_VALUES} . "$@"
init-python-project "$@" ${INIT_PYTHON_PROJECT_ARGS} --defaults --yes --verbose
$(MAKE) example-setup EXAMPLE_DIR="$@"

docs/examples/mkdocs: COPIER_DEFAULT_VALUES+=-d docs=mkdocs
docs/examples/sphinx: COPIER_DEFAULT_VALUES+=-d docs=sphinx
docs/examples/minimal: COPIER_DEFAULT_VALUES+=-d docs=none -d use_precommit=False -d use_bumpversion=False
docs/examples/full: COPIER_DEFAULT_VALUES+=-d docs=mkdocs -d use_precommit=True -d use_bumpversion=True
docs/examples/gitlab: COPIER_DEFAULT_VALUES+=-d remote=gitlab-iis

$(DOC_EXAMPLES):
docs/examples/mkdocs: INIT_PYTHON_PROJECT_ARGS+=--docs mkdocs
docs/examples/sphinx: INIT_PYTHON_PROJECT_ARGS+=--docs sphinx
docs/examples/minimal: INIT_PYTHON_PROJECT_ARGS+=--docs none --no-precommit --no-bumpversion
docs/examples/full: INIT_PYTHON_PROJECT_ARGS+=--docs mkdocs --precommit --bumpversion
docs/examples/gitlab: INIT_PYTHON_PROJECT_ARGS+=--docs mkdocs --precommit --bumpversion --remote gitlab-iis
doc-examples: $(DOC_EXAMPLES)
$(DOC_EXAMPLES): uncopy-template copy-template
@echo "Recreating '$@'..."
@rm -rf "$@" && mkdir -p "$@"
@copier copy ${COPIER_ARGS} --defaults -d user_name=mkj ${COPIER_DEFAULT_VALUES} . "$@"
init-python-project "$@" --user-name mkj ${INIT_PYTHON_PROJECT_ARGS} --defaults --yes --verbose
@cd $@ &&\
python -m venv .venv || echo "Couldn't setup virtual environment" &&\
. .venv/bin/activate &&\
Expand Down Expand Up @@ -66,7 +64,7 @@ examples-clean: ## remove all published examples

build/example: ## build individual example for manual testing (will prompt for values!)
rm -rf "$@"
copier copy ${COPIER_ARGS} ${COPIER_DEFAULT_VALUES} . "$@"
init-python-project "$@" ${INIT_PYTHON_PROJECT_ARGS}
$(MAKE) example-setup EXAMPLE_DIR="$@"


Expand Down Expand Up @@ -124,8 +122,10 @@ install-build: build
copy-template:
@cp -r ${TEMPLATE_SRC} ${TEMPLATE_DEST}
@cp copier.yaml ${PKGDIR}/.
build-clean: ## remove build artifacts
@rm -rf ${BUILDDIR} ${PKGDIR}/template ${PKGDIR}/copier.yaml
uncopy-template:
@rm -rf ${TEMPLATE_DEST} ${PKGDIR}/copier.yaml
build-clean: uncopy-template ## remove build artifacts
@rm -rf ${BUILDDIR}

.PHONY: release release-test release-tag release-pypi release-github
release: release-test release-tag build release-pypi release-github
Expand Down
6 changes: 3 additions & 3 deletions copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ package_name:
Finally, the package name is repeated across multiple configuration and documentation files.
use_precommit:
precommit:
type: bool
default: true
help: Use pre-commit to run checks on each commit?
Expand All @@ -64,7 +64,7 @@ use_precommit:
Most formatters and some linters are able to fix issues automatically.
So you can simply review the changes those tools made, stage them and commit again.
use_bumpversion:
bumpversion:
type: bool
default: false
help: Use bumpversion to manage semantic version across multiple files?
Expand Down Expand Up @@ -178,4 +178,4 @@ _tasks:
- "rm -rf context"
- "git init --initial-branch={{default_branch}}"
- "git remote add origin {{remote_url}} || true"
- "{% if use_precommit %}pre-commit install || echo 'Error during installation of pre-commit hooks. Is pre-commit installed?'{% endif %}"
- "{% if precommit %}pre-commit install || echo 'Error during installation of pre-commit hooks. Is pre-commit installed?'{% endif %}"
22 changes: 14 additions & 8 deletions docs/user-guide/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
{{ includex('README.md', start_match='Prerequisites', end_match='<!-- usage-end -->')}}

## Using [copier] directly
??? note "Using [pipx]"

```console
copier copy --trust https://git01.iis.fhg.de/mkj/project-template.git my_new_project
```
```{.sh .copy}
pipx run init-python-project
```

*Note: `--trust` is required because the template uses [tasks][] to setup your git repository for you.*
[pipx]: https://pypa.github.io/pipx/

[tasks]: https://git01.iis.fhg.de/mkj/project-template/-/blob/main/copier.yaml
??? note "Using [copier]"

The underlying template is built using [copier]. This means you can also use the copier template directly like this:

*Note: If you have [pipx][] installed (you should, it is good), you can simply use `pipx run copier` out of the box.*
```{.sh .copy}
copier copy --trust https://git01.iis.fhg.de/mkj/project-template.git my_new_project
```

*Note: `--trust` is required because the template uses [tasks] to setup your git repository for you.*

[tasks]: https://git01.iis.fhg.de/mkj/project-template/-/blob/main/copier.yaml
[copier]: https://github.com/copier-org/copier
[pipx]: https://pypa.github.io/pipx/
135 changes: 132 additions & 3 deletions src/init_python_project/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import logging
import sys
from enum import StrEnum
from pathlib import Path
from subprocess import check_output
from typing import Annotated, Optional

import typer
from copier import run_copy
from typer import Argument, Option, Typer, colors, confirm, style

Expand All @@ -16,18 +20,124 @@ def version_callback(value: bool) -> None:
sys.exit(0)


class DocumentationTool(StrEnum):
"which documentation tool to use"
mkdocs = "mkdocs"
sphinx = "sphinx"
none = "none"


class DocumentationTemplate(StrEnum):
"which documentation template to use"
sphinx_fhg_iis = "sphinx-fhg-iis"
builtin = "none"


class RemotePlatform(StrEnum):
"which remote platform to configure"
github = "github"
gitlab_fhg = "gitlab-fhg"
gitlab_iis = "gitlab-iis"


def CustomOptional(_type=bool, help="", custom_flag: str | list = None, **kwargs):
if issubclass(_type, StrEnum):
kwargs = {"case_sensitive": False, **kwargs}
if not help:
help = _type.__doc__

kwargs = {"show_default": False, "help": help, **kwargs}

if custom_flag is None:
return Annotated[Optional[_type], Option(**kwargs)]

if isinstance(custom_flag, str):
custom_flag = [custom_flag]
return Annotated[Optional[_type], Option(*custom_flag, **kwargs)]


@app.command(name="init-python-project")
def cli(
# data passed to the underlying copier template
target_path: Path = Argument("new-project"),
project_name: CustomOptional(str, "project name (title case with spaces)") = None,
package_name: CustomOptional(str, "Python package name (lowercase with underscores)") = None,
user_name: CustomOptional(str, "your user name") = None,
docs: CustomOptional(DocumentationTool) = None,
docs_template: CustomOptional(DocumentationTemplate) = None,
remote: CustomOptional(RemotePlatform) = None,
remote_url: CustomOptional(str, "ssh url where your repository will be hosted on") = None,
precommit: CustomOptional(bool, "include pre-commit hooks") = None,
bumpversion: CustomOptional(bool, "include bumpversion configuration") = None,
# arguments that affect project creation
defaults: Annotated[
bool, Option("--defaults", "-d", help="automatically accept all default options")
] = False,
dry_run: Annotated[bool, Option("--dry-run", help="do not actually create project")] = False,
always_confirm: Annotated[
bool, Option("--yes", "-y", help="answer any confirmation request with yes")
] = False,
version: Annotated[
Optional[bool], Option("--version", callback=version_callback, is_eager=True)
Optional[bool],
Option("--version", callback=version_callback, is_eager=True, help="show version and exit"),
] = None,
verbose: Annotated[
Optional[bool],
typer.Option(
"--verbose",
"-v",
callback=lambda x: logging.basicConfig(
level=logging.INFO if x else logging.WARN, format="%(message)s"
),
is_eager=True,
help="show more information",
),
] = False,
copier_args: Annotated[
Optional[list[str]],
typer.Option("--copier-arg", help="anything you want to pass to copier"),
] = None,
) -> None:
"""Executes the CLI command to create a new project."""
target_path.mkdir(exist_ok=True)
"""Executes the CLI command to create a new project.
For a list of supported copier arguments, see
https://copier.readthedocs.io/en/stable/reference/main/#copier.main.Worker.
Note that `src_path`, `dest_path`, `vcs_ref`, `data`, `defaults`, `user_defaults` and `unsafe`
are already set by this command. Further, `--dry-run` corresponds to copier's `--pretend` and
`--yes` implies copier's `--overwrite`.
"""

if docs_template not in [None, "none"] and (
docs is None or (docs is not None and not docs_template.value.startswith(docs.value))
):
typer.secho(
f"Error: selected template ({docs_template}) not compatible "
f"with documentation tool ({docs})",
fg=colors.RED,
err=True,
)
raise typer.Exit(1)

# cast enums to their values
for option in "docs remote".split():
if locals()[option] is not None:
locals()[option] = locals()[option].value

# assemble values provided by the user
data = {}
for (
option
) in "project_name package_name user_name docs remote remote_url precommit bumpversion".split():
value = locals()[option]
if value is not None:
logging.info("%s: %s", option, value)
data[option] = value

if (
target_path.is_dir()
and any(target_path.iterdir())
and not always_confirm
and not confirm(
style(
f"Target directory '{target_path}' is not empty! Continue?",
Expand All @@ -37,10 +147,29 @@ def cli(
):
sys.exit(1)

# parse copier args
copier_args = {
k.replace("--", "").replace("-", "_"): v
for k, v in (
arg.split("=") if "=" in arg else arg.split() if " " in arg else (arg, True)
for arg in (copier_args or [])
)
}

run_copy(
src_path=str(Path(__file__).parent.absolute()),
dst_path=target_path,
unsafe=True,
data=data,
user_defaults=dict(
user_name=check_output(["whoami"]).decode().strip() if user_name is None else user_name,
project_name=target_path.name.replace("-", " ").replace("_", " ").title(),
),
defaults=defaults,
overwrite=always_confirm,
pretend=dry_run or copier_args.pop("pretend", False),
quiet=True,
**copier_args,
)


Expand Down
4 changes: 2 additions & 2 deletions template/Makefile.jinja
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: install-dev
install-dev: ## install project including all development dependencies
pip install -e .[test,dev]
pip install -r docs/requirements.txt
pip install -e .[test,dev]{% if docs != 'none' %}
pip install -r docs/requirements.txt{% endif %}

.PHONY: maintainability
maintainability: ## run maintainability checks
Expand Down
2 changes: 1 addition & 1 deletion template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies = ["click"]
# Similar to `dependencies` above, these must be valid existing
# projects.
[project.optional-dependencies]
dev = ["black", "radon", "ruff"{% if use_bumpversion %}, "bump2version"{% endif %}]
dev = ["black", "radon", "ruff"{% if bumpversion %}, "bump2version"{% endif %}]
test = ["pytest", "pytest-cov", "coverage[toml]"]

# The following would provide a command line executable which executes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mkdocs
mkdocs-material
mkdocstrings[python]
mkdocs-git-revision-date-localized-plugin
Expand Down
18 changes: 9 additions & 9 deletions tests/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def venv(tmp_path):


@pytest.mark.slow
@pytest.mark.parametrize("use_precommit", [True, False], ids=["pre-commit", "no pre-commit"])
@pytest.mark.parametrize("precommit", [True, False], ids=["pre-commit", "no pre-commit"])
@pytest.mark.parametrize(
"docs,docs_template",
SUPPORTED_DOCS_TEMPLATES_COMBINATIONS,
Expand All @@ -48,7 +48,7 @@ def venv(tmp_path):
def test_template_generation(
venv: VirtualEnvironment,
tmp_path: Path,
use_precommit: bool,
precommit: bool,
docs: str,
docs_template: str,
remote: str,
Expand All @@ -59,7 +59,7 @@ def test_template_generation(
str(tmp_path),
data=dict(
**required_static_data,
use_precommit=use_precommit,
precommit=precommit,
docs=docs,
docs_template=docs_template,
remote=remote,
Expand All @@ -81,7 +81,7 @@ def test_template_generation(
assert fp_changelog.is_file(), "new projects should have a CHANGELOG file"

fp_precommit_config = tmp_path / ".pre-commit-config.yaml"
assert fp_precommit_config.is_file() == use_precommit
assert fp_precommit_config.is_file() == precommit

fp_git = tmp_path / ".git"
assert fp_git.is_dir(), "new projects should be git repositories"
Expand Down Expand Up @@ -318,21 +318,21 @@ def read_last_commit_msg(cwd: Path | str = None):
return check_output(["git", "log", "-1", "--pretty=%B"], cwd=str(cwd or ".")).decode().strip()


@pytest.mark.parametrize("use_bumpversion", [True, False], ids=["bumpversion", "no bumpversion"])
def test_bumpversion_option(venv: VirtualEnvironment, tmp_path: Path, use_bumpversion: bool):
@pytest.mark.parametrize("bumpversion", [True, False], ids=["bumpversion", "no-bumpversion"])
def test_bumpversion_option(venv: VirtualEnvironment, tmp_path: Path, bumpversion: bool):
run_copy(
str(fp_template),
str(tmp_path),
data=dict(
**required_static_data,
use_bumpversion=use_bumpversion,
use_precommit=False, # makes testing easier
bumpversion=bumpversion,
precommit=False, # makes testing easier
),
unsafe=True,
defaults=True,
vcs_ref="HEAD",
)
if not use_bumpversion:
if not bumpversion:
assert not (tmp_path / ".bumpversion.cfg").is_file()
return

Expand Down

0 comments on commit 5917679

Please sign in to comment.