diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..97c0f19f
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "template/{% if docs_template == 'sphinx-fhg-iis' %}docs{% endif %}"]
+ path = "template/{% if docs_template == 'sphinx-fhg-iis' %}docs{% endif %}"
+ url = https://git01.iis.fhg.de/mkj/sphinx_template
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 90ec3c67..88a2b1a2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,6 +12,7 @@ repos:
hooks:
- id: prettier
types_or: [json, yaml, css, javascript]
+ pass_filenames: false
# black should have the final say on python formatting, so it comes last
- repo: https://github.com/psf/black
rev: 23.9.1
@@ -22,7 +23,7 @@ repos:
hooks:
- id: pytest
name: pytest
- entry: pytest -n auto -m "not slow"
+ entry: make test
language: system
pass_filenames: false
files: "^template/"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21aa5e1b..b2d29f0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]
+### Added
+
+- github ci now runs tests, collects coverage and creates maintainability and coverage badges
+- add [sphinx_template](https://git01.iis.fhg.de/sch/sphinx_template/) as an option when choosing sphinx for documentation
+
+### Changed
+
+- template now uses a static documentation badge provided by shields.io
+
+### Fixed
+
+- link to pipeline in README now correctly links to github actions
+- when bumpversion is selected, add `bump2version` to dev dependencies
+
## [0.0.2] - 2023-09-19
### Added
diff --git a/Makefile b/Makefile
index 74b27ea2..2cc26223 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ DOC_EXAMPLES = docs/examples/mkdocs docs/examples/sphinx docs/examples/default d
examples: ## build all published examples
examples: $(PUBLISHED_EXAMPLES)
-COPIER_ARGS?=--trust
+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
build/examples/%: EXAMPLE_DIR:=$@
@@ -99,10 +99,12 @@ spellcheck-dump: ## save all flagged words to project terms dictionary
.PHONY: test
-PYTEST_ARGS=-n auto
-test: ## run tests quickly
+PYTEST_ARGS?=
+test: ## run some tests
+test: build-clean copy-template
pytest ${PYTEST_ARGS} -m "not slow"
test-all: ## run all tests
+test-all: build-clean copy-template
pytest ${PYTEST_ARGS}
@@ -123,7 +125,7 @@ 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
+ @rm -rf ${BUILDDIR} ${PKGDIR}/template ${PKGDIR}/copier.yaml
.PHONY: release release-test release-tag release-pypi release-github
release: release-test release-tag build release-pypi release-github
diff --git a/README.md b/README.md
index ea2bfa9a..1a1098ad 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
Python Project Template
-[![](https://img.shields.io/badge/python-3.11-blue)][sample project]
+[![](https://img.shields.io/badge/Documentation-main-blue)][docs]
[![](https://img.shields.io/badge/Example-Sample_Project-blue)][sample project]
[![PyPI - Version](https://img.shields.io/pypi/v/init-python-project)][pypi]
@@ -63,6 +63,7 @@ init-python-project
The first part of the user guide consists of tutorials on how to answer the template questions for [Your First Project][], what [Next Steps][] there are after your project is created and why the [Project Structure][] looks like it does.
+[docs]: https://jannismain.github.io/python-project-template/
[your first project]: https://jannismain.github.io/python-project-template/user-guide/first-project
[next steps]: https://jannismain.github.io/python-project-template/user-guide/first-project
[project structure]: https://jannismain.github.io/python-project-template/user-guide/project-structure
diff --git a/copier.yaml b/copier.yaml
index e0d62d66..bb0ba671 100644
--- a/copier.yaml
+++ b/copier.yaml
@@ -98,6 +98,17 @@ docs:
If you are not sure which one to use, simply go with the default 😉.
+docs_template:
+ type: str
+ choices:
+ Fraunhofer IIS Sphinx Template: sphinx-fhg-iis
+ None: none
+ default: "none"
+ when: "{{ docs == 'sphinx' }}"
+ help: Which documentation template do you want to use?
+ explanation: |
+ See [Fraunhofer IIS Sphinx Template](https://git01.iis.fhg.de/sch/sphinx_template/).
+
remote:
choices:
GitHub: github
diff --git a/pyproject.toml b/pyproject.toml
index 2ff6b577..eea793b2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "init-python-project"
-description = "A python project generator based on copier."
+description = "A python project generator."
readme = "README.md"
requires-python = ">=3.11"
authors = [{ name = "Jannis Mainczyk", email = "jmainczyk@gmail.com" }]
@@ -50,9 +50,10 @@ init-python-project = "init_python_project.cli:app"
[tool.pytest.ini_options]
minversion = "6.0"
-addopts = "-ra -q"
+addopts = "-ra -q -n auto --exitfirst"
testpaths = ["tests"]
markers = ["slow"]
+filterwarnings = ["ignore::copier.vcs.DirtyLocalWarning"]
[tool.black]
line-length = 100
diff --git a/src/init_python_project/copier.yaml b/src/init_python_project/copier.yaml
deleted file mode 100644
index e0d62d66..00000000
--- a/src/init_python_project/copier.yaml
+++ /dev/null
@@ -1,170 +0,0 @@
-_subdirectory: template
-
-project_name:
- type: str
- help: What is the name of your project?
- placeholder: Sample Project
- expected: Title case string, can contain spaces
- explanation: |
- The project name will be used to propose a suitable package name and [Remote Url][remote-url].
- It is also repeated in multiple places (Python package configuration, documentation, etc.).
-
-package_name:
- type: str
- help: What is the name of your Python package?
- default: "{{ project_name|lower|replace(' ', '_')|replace('-', '_') }}"
- expected: Lowercase string, can contain underscores
- explanation: |
- This is the name of your Python package. As such, it will be used as the name of the directory containing your code.
- All imports of your package start with this name.
-
- Example: If you choose `sample_project` as your package name, your code files would be created in
-
- ```
- ./
- ├── src
- │ └── sample_project
- │ ├── __init__.py
- │ ├── __main__.py
- │ └── some_module.py
- ├── pyproject.toml
- └── ...
- ```
-
- and your imports would look like this:
-
- ```python
- from sample_project import some_module
- ```
-
- You might want to consult [PEP 423 – Naming conventions and recipes related to packaging](https://peps.python.org/pep-0423/)
- for guidance on Python package name conventions.
-
- The cli command included with this template will be named after your package, only with dashes instead of underscores.
- Following the example above, your cli would then be available as
-
- ```console
- $ sample-project --help
- ```
-
- If you ever want to publish your Python package to [PyPI](https://pypi.org), the package name has to be unique to be accepted there.
-
- Finally, the package name is repeated across multiple configuration and documentation files.
-
-use_precommit:
- type: bool
- default: true
- help: Use pre-commit to run checks on each commit?
- explanation: |
- [pre-commit](../reference/tooling/pre-commit.md) is a tool that makes configuring [git hooks][git-hooks] a lot easier.
-
- This template uses pre-commit hooks to ensure, all changes match the expected formatting and style.
- Additionally, running linters at this stage can prevent committing something that contains obvious issues.
-
- 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:
- type: bool
- default: false
- help: Use bumpversion to manage semantic version across multiple files?
- explanation: |
- If you want to version your project, [bumpversion][] provides an easy way to increase version numbers across multiple files.
-
- It integrates with git and helps with your release workflow by automating version bump, commit and tag creation.
- Used correctly, releasing a new version of your project can be done with a single command.
-
- It also helps to follow [semantic versioning][semantic-versioning] guidelines when increasing your version numbers.
-
-docs:
- type: str
- choices:
- Material for MkDocs: mkdocs
- Sphinx: sphinx
- None: none
- default: "mkdocs"
- help: Which documentation tool do you want to use?
- explanation: |
- [Documentation][documentation] is an important part of any project.
-
- So far, this template supports two documentation tools: [MkDocs][mkdocs] and [Sphinx][sphinx].
-
- [MkDocs][mkdocs] is a great documentation tool built around [Markdown][markdown].
- It is easy to use and produces a great looking documentation website with minimal overhead and configuration.
-
- [Sphinx][sphinx] is a powerful tool for documentation written in [Markdown][markdown] or [reStructuredText][restructuredtext].
- With the [`myst_parser`][myst_parser] it is now (almost) possible to rely on Markdown only.
- Choose this if you want to publish your documentation in multiple formats (e.g. PDF, HTML, ePub, ...).
-
- If you are not sure which one to use, simply go with the default 😉.
-
-remote:
- choices:
- GitHub: github
- FHG Gitlab: gitlab-fhg
- IIS Gitlab: gitlab-iis
- help: Which platform will your project be hosted on?
- default: "github"
- explanation: |
- This template provides a CI configuration for either GitHub (Github Actions) or GitLab (Gitlab CI).
-
- Also, the link to your documentation depends on which remote you choose.
-
- If you want to push your project to multiple remotes, you can add them later.
-
-user_name:
- type: str
- help: User name (the one you used with your hosted git provider)
- explanation: |
- This is the user name you are using with the remote provider you provided in the previous question.
-
- Together with [remote][], it will be used to suggest a [remote url][remote-url] for your project.
-
-remote_url:
- type: str
- help: URL of the remote repository
- default: git@{% if remote=='github' %}github.com{% elif remote=='gitlab-iis' %}git01.iis.fhg.de{% elif remote=='gitlab-fhg' %}gitlab.cc-asp.fraunhofer.de{% endif %}:{{user_name}}/{{project_name | lower | replace(' ', '-')}}.git
- expected: SSH URL to your remote repository
- explanation: |
- Apart from configuring the git remote for you, the remote URL is required to determine other values, such as
-
- - Links in your README and CHANGELOG files
- - Links to your documentation
- - Link to your repository from your documentation
- - ...
-
- If you did not create your remote repository yet (i.e. new project at GitHub or Gitlab), this might be a good time to do so.
-
- !!! tip "Create empty repository"
-
- Do not initialize your remote project with a LICENSE or README file.
- This will let you push your initial commit without merge or rebase.
-
- === "Gitlab"
-
- ![](https://cln.sh/gwzwgtHH+)
- *The SSH URL to your Gitlab repository can be found under the `Clone` dropdown (screenshot taken 23.08.23)*{.caption}
-
-
- === "GitHub"
-
- ![](https://cln.sh/SscJVB6N+)
- *The SSH URL to your GitHub repository can be found under the `<> Code` dropdown menu (screenshot taken 23.08.23)*{.caption}
-
-default_branch:
- type: str
- default: main
- help: Name of the initial git branch that will be created
- expected: Lowercase string, can contain dashes
- examples: [main, master, dev]
- explanation: |
- Name of the [initial branch][] of your new project.
-
- This branch traditionally was called `master`, but is more often called `main` now.
-
- [initial branch]: https://git-scm.com/docs/git-init#Documentation/git-init.txt---initial-branchltbranch-namegt
-_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 %}"
diff --git a/template/Makefile.jinja b/template/Makefile.jinja
index a12bc283..64cdbf1f 100644
--- a/template/Makefile.jinja
+++ b/template/Makefile.jinja
@@ -1,6 +1,7 @@
.PHONY: install-dev
install-dev: ## install project including all development dependencies
- pip install -e .[test,doc,dev]
+ pip install -e .[test,dev]
+ pip install -r docs/requirements.txt
.PHONY: maintainability
maintainability: ## run maintainability checks
@@ -35,7 +36,7 @@ docs-live: ## serve documentation
{%- if docs == 'mkdocs' %}
mkdocs serve
{%- elif docs == 'sphinx' %}
- sphinx-autobuild docs ${DOCS_TARGET}/livehtml
+ cd docs && $(MAKE) serve
{% endif %}
{% endif %}
diff --git a/template/README.md.jinja b/template/README.md.jinja
index 8e9aafe4..c4682de5 100644
--- a/template/README.md.jinja
+++ b/template/README.md.jinja
@@ -2,11 +2,14 @@
{%- import 'template/context' as ctx with context %}
-[![documentation][badge_documentation]]({{ctx.remote_url_pages}}) [![badge_pipeline][]]({{ctx.remote_url_https}}/-/pipelines) [![badge_coverage][]]({{ctx.remote_url_pages}}/coverage) [![badge_maintainability][]]()
-
-[badge_documentation]: {{ctx.remote_url_pages}}/badges/documentation.svg
-[badge_coverage]: {{ctx.remote_url_https}}/badges/{{default_branch}}/coverage.svg
-[badge_pipeline]: {{ctx.remote_url_https}}/badges/{{default_branch}}/pipeline.svg
+[![badge_documentation][]][documentation] [![badge_pipeline][]][pipeline] [![badge_coverage][]][coverage] [![badge_maintainability][]]()
+
+[documentation]: {{ctx.remote_url_pages}}
+[badge_documentation]: https://img.shields.io/badge/Documentation-{{default_branch}}-blue
+[coverage]: {{ctx.remote_url_pages}}/coverage
+[badge_coverage]: {{ctx.remote_url_coverage_badge}}
+[badge_pipeline]: {{ctx.remote_url_pipeline_badge}}
+[pipeline]: {{ctx.remote_url_pipeline}}
[badge_maintainability]: {{ctx.remote_url_pages}}/badges/maintainability.svg
diff --git a/template/context b/template/context
index 91cf72d1..57d05f78 100644
--- a/template/context
+++ b/template/context
@@ -15,11 +15,30 @@
{% if remote == 'github' %}
{% set domain_pages = 'github.io' %}
+{% set path_pipeline = '/actions?query=branch%3A' + default_branch %}
{% elif remote == 'gitlab-iis' %}
{% set domain_pages = domain %}
{% elif remote == 'gitlab-fhg' %}
{% set domain_pages = 'pages.fraunhofer.de' %}
{% endif %}
+
+{% if remote.startswith('gitlab') %}
+{% set path_pipeline = '/-/pipelines' %}
+{% endif %}
+
{% set remote_url_pages = "https://" + group + "." + domain_pages + "/" + pages_path %}
+{% set remote_url_pipeline = remote_url_https + path_pipeline %}
+
+{% if remote == 'github' %}
+{# coverage badge is provided by gitlab #}
+## coverage badge is generated by github action and published on github pages
+{% set remote_url_coverage_badge = remote_url_pages + '/badges/coverage.svg' %}
+## pipeline badge is provided by github
+{% set remote_url_pipeline_badge = remote_url_https + '/actions/workflows/ci.yaml/badge.svg' %}
+{% else %}
+## coverage and pipeline badges are provided by gitlab
+{% set remote_url_coverage_badge = remote_url_https + '/badges/' + default_branch + 'coverage.svg' %}
+{% set remote_url_pipeline_badge = remote_url_https + '/badges/' + default_branch + 'pipeline.svg' %}
+{% endif %}
{% set cli_command = package_name | replace("_", "-") %}
diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja
index 82492780..bfa8c0c8 100644
--- a/template/pyproject.toml.jinja
+++ b/template/pyproject.toml.jinja
@@ -70,24 +70,8 @@ dependencies = ["click"]
# Similar to `dependencies` above, these must be valid existing
# projects.
[project.optional-dependencies]
-dev = ["black", "radon", "ruff"]
+dev = ["black", "radon", "ruff"{% if use_bumpversion %}, "bump2version"{% endif %}]
test = ["pytest", "pytest-cov", "coverage[toml]"]
-{% if docs!="none" -%}
-doc = [
-{%- if docs=="mkdocs" %}
- "mkdocs-material",
- "mkdocstrings[python]",
- "mkdocs-git-revision-date-localized-plugin",
- "mkdocs-macros-plugin",
-{%- elif docs=="sphinx" %}
- "sphinx",
- "furo",
- "myst_parser",
- "sphinx-autodoc2",
- "sphinx-autobuild",
-{%- endif %}
-]
-{%- endif %}
# The following would provide a command line executable which executes
# the function `main` from this package's cli module when invoked.
diff --git a/template/{% if docs == 'mkdocs' %}docs{% endif %}/requirements.txt b/template/{% if docs == 'mkdocs' %}docs{% endif %}/requirements.txt
new file mode 100644
index 00000000..5090e64c
--- /dev/null
+++ b/template/{% if docs == 'mkdocs' %}docs{% endif %}/requirements.txt
@@ -0,0 +1,4 @@
+mkdocs-material
+mkdocstrings[python]
+mkdocs-git-revision-date-localized-plugin
+mkdocs-macros-plugin
diff --git a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/.gitlab/docs.yml b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/.gitlab/docs.yml
new file mode 100644
index 00000000..6e536da8
--- /dev/null
+++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/.gitlab/docs.yml
@@ -0,0 +1,10 @@
+docs:
+ image: python:latest
+ script:
+ - pip install -r docs/requirements.txt
+ - pushd docs && BUILDDIR=_build make html && popd
+ - mkdir -p build/docs
+ - mv docs/_build/html/* build/docs
+ artifacts:
+ paths:
+ - build/docs
diff --git a/template/{% if docs == 'sphinx' %}docs{% endif %}/Makefile b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/Makefile
similarity index 68%
rename from template/{% if docs == 'sphinx' %}docs{% endif %}/Makefile
rename to template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/Makefile
index 76bb8dbc..cf7e036c 100644
--- a/template/{% if docs == 'sphinx' %}docs{% endif %}/Makefile
+++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/Makefile
@@ -3,16 +3,20 @@
# You can set these variables from the command line, and also
# from the environment for the first two.
-SPHINXOPTS ?=
-SPHINXBUILD ?= sphinx-build
-SOURCEDIR = .
-BUILDDIR ?= ../build/docs
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SPHINXAUTOBUILD ?= sphinx-autobuild
+SOURCEDIR = .
+BUILDDIR ?= ../build/docs
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-.PHONY: help Makefile
+serve:
+ @$(SPHINXAUTOBUILD) "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help serve Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
diff --git a/template/{% if docs == 'sphinx' %}docs{% endif %}/_static/.gitkeep b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/_static/.gitkeep
similarity index 100%
rename from template/{% if docs == 'sphinx' %}docs{% endif %}/_static/.gitkeep
rename to template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/_static/.gitkeep
diff --git a/template/{% if docs == 'sphinx' %}docs{% endif %}/changelog.md b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/changelog.md
similarity index 100%
rename from template/{% if docs == 'sphinx' %}docs{% endif %}/changelog.md
rename to template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/changelog.md
diff --git a/template/{% if docs == 'sphinx' %}docs{% endif %}/conf.py.jinja b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja
similarity index 100%
rename from template/{% if docs == 'sphinx' %}docs{% endif %}/conf.py.jinja
rename to template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/conf.py.jinja
diff --git a/template/{% if docs == 'sphinx' %}docs{% endif %}/index.md b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md
similarity index 100%
rename from template/{% if docs == 'sphinx' %}docs{% endif %}/index.md
rename to template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/index.md
diff --git a/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt
new file mode 100644
index 00000000..4df05720
--- /dev/null
+++ b/template/{% if docs == 'sphinx' and docs_template=='none' %}docs{% endif %}/requirements.txt
@@ -0,0 +1,5 @@
+sphinx
+furo
+myst_parser
+sphinx-autodoc2
+sphinx-autobuild
diff --git a/template/{% if remote == 'github' %}.github{% endif %}/workflows/ci.yaml.jinja b/template/{% if remote == 'github' %}.github{% endif %}/workflows/ci.yaml.jinja
new file mode 100644
index 00000000..6c1d0b19
--- /dev/null
+++ b/template/{% if remote == 'github' %}.github{% endif %}/workflows/ci.yaml.jinja
@@ -0,0 +1,98 @@
+name: CI
+
+on:
+ push:
+ branches: ["{{default_branch}}"]
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Install project (including test requirements)
+ run: pip install .[test]
+ - name: Run tests and collect coverage
+ run: |
+ make coverage
+ - run: coverage xml
+ - run: pip install genbadge[coverage]
+ - run: genbadge coverage -i coverage.xml -o build/coverage/coverage.svg
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: coverage
+ path: build/coverage
+ maintainability:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Build maintainability badge
+ run: |
+ pip install anybadge radon
+ score=$(python -m radon cc --total-average src | tail -n 1 | cut -d' ' -f 3-4)
+ [[ "$score" = A* ]] && color="green"; [[ "$score" = B* ]] && color="green"
+ [[ "$score" = C* ]] && color="yellow"; [[ "$score" = D* ]] && color="orange_2"
+ [[ "$score" = E* ]] && color="orange"; [[ "$score" = F* ]] && color="orangered"
+ python -m anybadge --label=Maintainability --value="$score" --color="$color" -f maintainability -o
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: maintainability
+ path: maintainability.svg
+ {%- if docs != 'none' %}
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Install doc requirements
+ run: pip install -r docs/requirements.txt
+ - name: Build docs
+ run: make docs
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: docs
+ path: build/docs/html
+ {%- endif %}
+ deploy:
+ environment:
+ name: github-pages
+ url: {% raw %}${{steps.deployment.outputs.page_url}}{% endraw %}
+ runs-on: ubuntu-latest
+ needs: [coverage, maintainability, docs]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Pages
+ uses: actions/configure-pages@v3
+ - uses: actions/download-artifact@master
+ with:
+ path: public
+ - run: ls -lR public
+ - run: mv public/maintainability public/badges
+ - run: mv public/coverage/coverage.svg public/badges/.
+ - run: mv public/docs/* public/. && rm -r public/docs || echo "no documentation found!"
+ - name: Upload Pages
+ uses: actions/upload-pages-artifact@v2
+ with:
+ path: public
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v2
diff --git a/template/{% if remote == 'github' %}.github{% endif %}/workflows/{% if docs != 'none' %}docs.yaml{% endif %}.jinja b/template/{% if remote == 'github' %}.github{% endif %}/workflows/{% if docs != 'none' %}docs.yaml{% endif %}.jinja
deleted file mode 100644
index 02399baf..00000000
--- a/template/{% if remote == 'github' %}.github{% endif %}/workflows/{% if docs != 'none' %}docs.yaml{% endif %}.jinja
+++ /dev/null
@@ -1,34 +0,0 @@
-name: deploy documentation to github pages
-
-on:
- push:
- branches: ["{{default_branch}}"]
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-jobs:
- # Single deploy job no building
- deploy:
- environment:
- name: github-pages
- url: {% raw %}${{steps.deployment.outputs.page_url}}{% endraw %}
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- - name: Setup Pages
- uses: actions/configure-pages@v3
- - name: Setup environment
- run: python -m venv env; env/bin/pip install .[doc]
- - name: Build docs
- run: {% if docs=='mkdocs' %}MKDOCS_BIN=env/bin/mkdocs {% elif docs=='sphinx' %}SPHINXBUILD=env/bin/sphinx-build {% endif %}make docs
- - name: Upload Artifact
- uses: actions/upload-pages-artifact@v2
- with:
- path: "build/docs/html"
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v2
diff --git a/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja b/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja
index db6c3e68..f33e6eeb 100644
--- a/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja
+++ b/template/{% if remote.startswith('gitlab') %}.gitlab-ci.yml{% endif %}.jinja
@@ -1,8 +1,6 @@
image: python:latest
-stages: [test, publish]
test:
- stage: test
cache: # reuse venv in subsequent jobs
key: $CI_JOB_NAME
paths:
@@ -13,18 +11,18 @@ test:
- source env/bin/activate
script:
- pip install .[test]
- - pytest --doctest-modules --cov --cov-config=pyproject.toml --cov-branch --cov-report term --cov-report html:build/coverage --junitxml=report.xml
- - coverage report
- - coverage xml
+ - pytest --doctest-modules --cov --cov-config=pyproject.toml --cov-branch --cov-report term --cov-report html:build/coverage --junitxml=report.xml --cov-report xml
- pip install anybadge==1.9.0
- mkdir -p build/badges
- pip install radon==5.1.0
- make maintainability
# generate a badge for the maintainability index with the total average of cyclomatic complexity as value
- - python -m anybadge --label=Maintainability --value="$(python -m radon cc --total-average -nc src | tail -n 1 | cut -d' ' -f 3) ($(python -m radon cc --total-average -nc src | tail -n 1 | cut -d' ' -f 4 | cut -c 2-4))" -f build/badges/maintainability -o
- # generate a badge for the documentation with the current version as label
- # - color: #1082C2 corresponds to the default "informational" color set by https://shields.io
- - python -m anybadge --label=Documentation --value="v$(python -m {{package_name}} --version)" --color "#1082C2" -f build/badges/documentation -o
+ - |
+ score=$(python -m radon cc --total-average src | tail -n 1 | cut -d' ' -f 3-4)
+ [[ "$score" = A* ]] && color="green"; [[ "$score" = B* ]] && color="green"
+ [[ "$score" = C* ]] && color="yellow"; [[ "$score" = D* ]] && color="orange_2"
+ [[ "$score" = E* ]] && color="orange"; [[ "$score" = F* ]] && color="orangered"
+ python -m anybadge --label=Maintainability --value="$score" --color="$color" -f build/badges/maintainability -o
coverage: '/TOTAL.+?(\d+\%)/'
artifacts:
when: always
@@ -37,23 +35,33 @@ test:
- build/coverage
- build/badges
- env/
-{% if docs != 'none' %}
+
+{% if docs == 'sphinx' -%}
+include: docs/.gitlab/docs.yml
+
+{% elif docs == 'mkdocs' %}
+docs:
+ script:
+ mkdocs build --clean --site-dir build/docs
+ artifacts:
+ paths:
+ - build/docs
+
+{% endif -%}
+
pages:
- stage: publish
- needs:
- - test
+ needs: [test{% if docs != 'none' %}, docs{% endif %}]
only:
- {{default_branch}}
- staging
script:
- - mkdir -p public
- - source env/bin/activate
- - pip install .[doc]
- - DOCS_TARGET=public make docs
- - mv public/html/* public/. && rm -r public/html
+ {% if docs != 'none' -%}
+ - mv build/docs public
+ {% else -%}
+ - mkdir public
+ {% endif -%}
- mv build/badges public/.
- mv build/coverage public/.
artifacts:
paths:
- public
-{% endif -%}
diff --git a/tests/test_cli.py b/tests/test_cli.py
index f449e735..07bccc95 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,4 +1,5 @@
import logging as log
+import re
import pytest
from init_python_project.cli import app
@@ -15,3 +16,9 @@ def test_default_values(cli):
result = cli("--help")
log.debug(result.output)
assert result.output.strip().startswith("Usage: init-python-project")
+
+
+def test_version(cli):
+ result = cli("--version")
+ log.debug(result.output)
+ assert re.match(r"^\d\.\d\.\d$", result.output.strip()), "should return semantic version number"
diff --git a/tests/test_package.py b/tests/test_package.py
index 0e797d9b..af4a3caa 100644
--- a/tests/test_package.py
+++ b/tests/test_package.py
@@ -11,8 +11,8 @@ def bin():
yield Path(os.getenv("BIN_PATH", "."))
+@pytest.mark.slow
def test_template_generation_via_cli(bin: Path, tmp_path: Path):
- # generate project
child = pexpect.spawn(str(bin / "init-python-project"), ["my-project"], cwd=tmp_path, timeout=3)
child.expect(".* project.*")
child.sendline("My Project")
@@ -27,7 +27,7 @@ def test_template_generation_via_cli(bin: Path, tmp_path: Path):
child.expect(".* platform.*")
child.sendline("") # accept default
child.expect(".* name.*")
- child.sendline("cool-user") # accept default
+ child.sendline("cool-user")
child.expect(".* remote.*")
child.sendline("") # accept default
child.expect(".* initial git branch.*")
diff --git a/tests/test_template.py b/tests/test_template.py
index f11c18de..0bbe7134 100644
--- a/tests/test_template.py
+++ b/tests/test_template.py
@@ -1,6 +1,8 @@
+import itertools
import os
+import tomllib
from pathlib import Path
-from subprocess import check_output
+from subprocess import check_call, check_output, run
import pytest
import yaml
@@ -10,6 +12,13 @@
template = yaml.safe_load(Path(__file__).parent.with_name("copier.yaml").read_text())
SUPPORTED_REMOTES = template["remote"]["choices"].values()
SUPPORTED_DOCS = template["docs"]["choices"].values()
+SUPPORTED_DOCS_TEMPLATES = template["docs_template"]["choices"].values()
+SUPPORTED_DOCS_TEMPLATES_COMBINATIONS = [
+ t
+ for t in itertools.product(SUPPORTED_DOCS, SUPPORTED_DOCS_TEMPLATES)
+ if t[1] == "none" or t[1].startswith(t[0])
+]
+"""All combinations of docs and docs_template options."""
fp_template = Path(__file__).parent.parent
@@ -26,19 +35,22 @@ def venv(tmp_path):
venv.create()
(venv.path / ".gitignore").unlink()
yield venv
+ print(tmp_path) # useful for debugging the built project
@pytest.mark.slow
@pytest.mark.parametrize("use_precommit", [True, False], ids=["pre-commit", "no pre-commit"])
-@pytest.mark.parametrize("use_bumpversion", [True, False], ids=["bumpversion", "no bumpversion"])
-@pytest.mark.parametrize("docs", SUPPORTED_DOCS)
+@pytest.mark.parametrize(
+ "docs,docs_template",
+ SUPPORTED_DOCS_TEMPLATES_COMBINATIONS,
+)
@pytest.mark.parametrize("remote", SUPPORTED_REMOTES)
def test_template_generation(
venv: VirtualEnvironment,
tmp_path: Path,
use_precommit: bool,
- use_bumpversion: bool,
docs: str,
+ docs_template: str,
remote: str,
project_name: str = "Sample Project",
):
@@ -48,12 +60,13 @@ def test_template_generation(
data=dict(
**required_static_data,
use_precommit=use_precommit,
- use_bumpversion=use_bumpversion,
docs=docs,
+ docs_template=docs_template,
remote=remote,
),
defaults=True,
unsafe=True,
+ vcs_ref="HEAD",
)
fp_readme = tmp_path / "README.md"
@@ -70,9 +83,6 @@ def test_template_generation(
fp_precommit_config = tmp_path / ".pre-commit-config.yaml"
assert fp_precommit_config.is_file() == use_precommit
- fp_bumpversion_config = tmp_path / ".bumpversion.cfg"
- assert fp_bumpversion_config.is_file() == use_bumpversion
-
fp_git = tmp_path / ".git"
assert fp_git.is_dir(), "new projects should be git repositories"
@@ -81,8 +91,12 @@ def test_template_generation(
fp_mkdocs_cfg = tmp_path / "mkdocs.yml"
assert fp_mkdocs_cfg.is_file(), "mkdocs configuration file should exist"
elif docs == "sphinx":
- fp_sphinx_cfg = fp_docs / "conf.py"
- assert fp_sphinx_cfg.is_file(), "sphinx configuration file should exist"
+ fp_sphinx_makefile = fp_docs / "Makefile"
+ assert fp_sphinx_makefile.is_file(), "sphinx Makefile file should exist"
+ fp_sphinx_requirements = fp_docs / "requirements.txt"
+ assert fp_sphinx_requirements.is_file(), "sphinx requirements file should exist"
+ fp_sphinx_ci_job = fp_docs / ".gitlab" / "docs.yml"
+ assert fp_sphinx_ci_job.is_file(), "sphinx ci job should exist"
use_docs = docs != "none"
assert fp_docs.is_dir() == use_docs, "docs directory should exist if configured"
@@ -94,32 +108,21 @@ def test_template_generation(
), "new projects should have a remote repository configured"
os.chdir(tmp_path)
+ if docs_template != "none":
+ # docs template needs to be formatted before we can assume
+ # that all pre-commit hooks pass
+ check_output(["git", "add", "."])
+ run(["pre-commit", "run", "--all-files"])
check_output(["git", "add", "."])
check_output(["git", "commit", "-m", "initial commit"])
# verify that example can be installed
- venv.install(".[doc,dev,test]", editable=True)
+ venv.install(".[dev,test]", editable=True)
venv_bin = Path(venv.bin)
# verify that pytest works and all tests pass
check_output([venv_bin / "pytest", "-q"])
- # verify docs can be built
- if use_docs:
- fp_docs_built = tmp_path / "build" / "docs" / "html"
- assert not fp_docs_built.is_dir()
- check_output(
- ["make", "docs"],
- env={
- "SPHINXBUILD": str(venv_bin / "sphinx-build"),
- "MKDOCS_BIN": str(venv_bin / "mkdocs"),
- },
- )
- assert fp_docs_built.is_dir(), "docs should have been built into build directory"
- assert (fp_docs_built / "index.html").is_file(), "index should exist"
-
- # TODO: Test template update
-
def test_default_branch_option(tmp_path: Path):
default_branch = "custom"
@@ -133,6 +136,7 @@ def test_default_branch_option(tmp_path: Path):
unsafe=True,
defaults=True,
user_defaults={"default_branch": default_branch},
+ vcs_ref="HEAD",
)
assert (
check_output(["git", "status", "--branch", "--porcelain"], cwd=str(tmp_path))
@@ -158,6 +162,7 @@ def test_remote_option(tmp_path: Path, remote: str):
),
unsafe=True,
defaults=True,
+ vcs_ref="HEAD",
)
git_remote_output = check_output(["git", "remote", "-v"], cwd=str(tmp_path)).decode()
@@ -192,20 +197,30 @@ def test_docs_option(venv: VirtualEnvironment, tmp_path: Path, docs: str):
),
defaults=True,
unsafe=True,
+ vcs_ref="HEAD",
)
if docs == "mkdocs":
fp_mkdocs_cfg = root / "mkdocs.yml"
assert fp_mkdocs_cfg.is_file(), "mkdocs configuration file should exist"
elif docs == "sphinx":
- fp_sphinx_cfg = root / "docs" / "conf.py"
- assert fp_sphinx_cfg.is_file(), "sphinx configuration file should exist"
+ fp_sphinx_makefile = root / "docs" / "Makefile"
+ assert fp_sphinx_makefile.is_file(), "sphinx Makefile should exist"
+ fp_sphinx_requirements = root / "docs" / "requirements.txt"
+ assert fp_sphinx_requirements.is_file(), "sphinx requirements file should exist"
+ fp_sphinx_ci_job = root / "docs" / ".gitlab" / "docs.yml"
+ assert fp_sphinx_ci_job.is_file(), "sphinx ci job should exist"
if docs != "none":
assert (root / "docs").is_dir(), "docs directory should exist"
+ fp_requirements = root / "docs" / "requirements.txt"
+ assert fp_requirements.is_file(), "doc requirements file should exist"
# install example including its doc requirements
- venv.install(f"{root}[doc]", editable=True)
+ venv.install(f"{root}", editable=True)
+ for req in fp_requirements.open().readlines():
+ if not req.strip().startswith("#"):
+ venv.install(req)
venv_bin = Path(venv.bin)
# verify docs can be built
@@ -225,7 +240,7 @@ def test_docs_option(venv: VirtualEnvironment, tmp_path: Path, docs: str):
@pytest.mark.parametrize("docs", SUPPORTED_DOCS)
@pytest.mark.parametrize("remote", SUPPORTED_REMOTES)
-def test_publish_docs_ci(venv: VirtualEnvironment, tmp_path: Path, docs: str, remote: str):
+def test_publish_docs_ci(tmp_path: Path, docs: str, remote: str):
root = tmp_path
run_copy(
@@ -238,15 +253,112 @@ def test_publish_docs_ci(venv: VirtualEnvironment, tmp_path: Path, docs: str, re
),
defaults=True,
unsafe=True,
+ vcs_ref="HEAD",
)
ci_platform = "gitlab" if remote.startswith("gitlab") else remote
+ docs_job = "docs"
if ci_platform == "gitlab":
- gitlab_ci_config = yaml.safe_load((root / ".gitlab-ci.yml").read_text())
+ ci_file = root / ".gitlab-ci.yml"
+ if docs == "sphinx":
+ # job for sphinx is included via separate file due to external template support
+ ci_file = root / "docs" / ".gitlab" / "docs.yml"
+ elif ci_platform == "github":
+ ci_file = root / ".github" / "workflows" / "ci.yaml"
+
+ assert ci_file.is_file()
+ ci_config = yaml.safe_load(ci_file.read_text())
+
+ if ci_platform == "github":
+ ci_config = ci_config["jobs"]
match (docs, ci_platform):
- case ("none", "github"):
- assert not (root / ".github" / "docs.yaml").is_file()
- case ("none", "gitlab"):
- assert "pages" not in gitlab_ci_config
+ case ("none", _):
+ assert docs_job not in ci_config, "docs job should not be present if docs are disabled"
+ case _:
+ assert docs_job in ci_config, "docs job should be present if docs are enabled"
+
+
+DOCS_WITH_TEMPLATE = [c for c in SUPPORTED_DOCS_TEMPLATES_COMBINATIONS if c[1] != "none"]
+"""Only those combinations that actually use a template."""
+
+
+@pytest.mark.parametrize("docs,docs_template", DOCS_WITH_TEMPLATE)
+def test_docs_with_template(tmp_path: Path, docs: str, docs_template: str):
+ root = tmp_path
+
+ run_copy(
+ str(fp_template),
+ str(root),
+ data=dict(
+ **required_static_data,
+ docs=docs,
+ docs_template=docs_template,
+ ),
+ defaults=True,
+ unsafe=True,
+ vcs_ref="HEAD",
+ )
+
+ docs = root / "docs"
+
+ docs_requirements = docs / "requirements.txt"
+ assert docs_requirements.is_file(), "all doc templates must come with a requirements file"
+
+ ci_job = docs / ".gitlab" / "docs.yml"
+ assert ci_job.is_file(), "doc templates must provide their ci job in separate file"
+
+
+def read_pyproject_version(path: Path):
+ return tomllib.load(path.open("rb"))["project"]["version"]
+
+
+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):
+ run_copy(
+ str(fp_template),
+ str(tmp_path),
+ data=dict(
+ **required_static_data,
+ use_bumpversion=use_bumpversion,
+ use_precommit=False, # makes testing easier
+ ),
+ unsafe=True,
+ defaults=True,
+ vcs_ref="HEAD",
+ )
+ if not use_bumpversion:
+ assert not (tmp_path / ".bumpversion.cfg").is_file()
+ return
+
+ assert (tmp_path / ".bumpversion.cfg").is_file()
+ fp_pyproject = tmp_path / "pyproject.toml"
+
+ os.chdir(tmp_path)
+ check_output(["git", "add", "."])
+ check_output(["git", "commit", "-m", "initial commit"])
+
+ venv.install(".[dev]", editable=True)
+ venv_bin = Path(venv.bin)
+
+ # verify that pytest works and all tests pass
+ check_output([venv_bin / "bumpversion", "-h"])
+
+ # bumpversion git interaction requires initial commit
+ run(["git", "add", "."])
+ run(["git", "commit", "-m", "initial commit", "--no-verify"])
+ assert read_pyproject_version(fp_pyproject) == "0.0.1"
+ check_call([venv_bin / "bumpversion", "patch"])
+ assert read_pyproject_version(fp_pyproject) == "0.0.2"
+ assert read_last_commit_msg() == "bump v0.0.1 -> v0.0.2"
+ check_call([venv_bin / "bumpversion", "minor"])
+ assert read_pyproject_version(fp_pyproject) == "0.1.0"
+ assert read_last_commit_msg() == "bump v0.0.2 -> v0.1.0"
+ check_output([venv_bin / "bumpversion", "major"])
+ assert read_pyproject_version(fp_pyproject) == "1.0.0"
+ assert read_last_commit_msg() == "bump v0.1.0 -> v1.0.0"