Modifying govcookiecutter
¶
govcookiecutter
uses the cookiecutter
Python package to build
template project structures. In turn, cookiecutter
uses Jinja templating to inject
user-defined variables into files, file names, and folder names. Most of these
variables are based on answers to prompts when you run the cookiecutter
command.
Validating user entries¶
User entries are validated with pre-generation hooks, which are defined in
hooks/pre_gen_project.py
. These hooks run before a project is created and, if they
fail, will not create the project.
The only supported validation currently is for a valid email address, based on the HTML5 standard for email address format.
Conditional files and/or folders¶
Conditional folders and/or files are items than only exist if actively selected for the
user. For example, if users select No
for the using_R
prompt, any R files and
content is removed from their outputted project.
This functionality is provided by post-generation hooks in govcookiecutter
, which are
defined in hooks/post_gen_project.py
. These hooks only run after a project has been
generated and, if they fail, will rollback the entire project.
Conditional files and folders are defined as features
in the
{{ cookiecutter.repo_name }}/manifest.json
file, which looks like:
{
"features": [
{
"name": "A name",
"description": "A description.",
"remove": {% if cookiecutter.{KEY} == {VALUE} %}true{% else %}false{% endif %},
"resources": ["A", "list", "of", "files", "and/or", "folders"]
}
]
}
where {KEY}
and {VALUE}
are cookiecutter.json
keys and values.
This works by using Jinja conditional templating to either set the remove
value to
true or false. The post-generation hook then scans through this JSON file deleting all
files and folders listed in the resources
value where remove == true
.
Changing conditional folders and files¶
If an existing feature has a remove
condition that meets your needs, amend its
resources
list to change the folders/files that will be removed.
To add a new feature, add a dictionary within the features
list, which has at least
the remove
and resources
keys. Add your Jinja conditional for the remove
value,
and a list of files/folders for the resources
key. For documentation purposes, it’s
good practice to add name
and description
keys as well!
To remove a feature, delete the appropriate dictionary from the features
list.
Conditional file content¶
Jinja conditional statements can be used display content based on the user responses. For example, for the following Markdown:
### `CONTRIBUTING.md`
The contributing guidelines for this project.
{% if cookiecutter.using_R == "Yes" -%}
### `DESCRIPTION`
R-specific information related to the project including the name, authors and packages
necessary for the project.
{% endif -%}
### `LICENSE`
The licence for this project...
the DESCRIPTION
section is conditional on the user response to the using_R
prompt.
Notice the hyphen before the trailing %
in each Jinja statement; this hyphen controls
blank space after the statement. A hyphen after the leading %
in a Jinja statement
controls blank space before the element.
Replacing folders and files¶
Replacing folders and files a more involved change, and is currently supported for
AQA frameworks and pull/merge request templates only.
These are performed in the hooks/post_gen_project.py
file.
Tests, coverage, and continuous integration¶
All pre- and post-generation hooks should be fully tested, alongside any generic
functions that we want to supply to users within the {{ cookiecutter.repo_name }}/src
package. These tests should be written in tests
or
{{ cookiecutter.repo_name }}/tests
as appropriate.
Coverage also only covers the hooks
and {{ cookiecutter.repo_name }}/src
folders.
Testing Jinja templating¶
Most of the tests are straightforward, and comprehensive. However, to test the Jinja
injection of user responses, the test_govcookiecutter_injected_variables.py
script
adopts a test-driven development approach to completeness.
This test parses all the content of the {{ cookiecutter.repo_name }}
folder, and
counts the number of times the replacement variable and its variations appear.
The constant dictionary variables at the top of the test script define the different variations of Jinja templating expected for each prompt, and their expected counts. The dictionary keys are replaced during the test with the test input variables.
If you modify the content, beware that these counts may change, so you will have to change these counts to pass the tests.
Continuous integration¶
Continuous integration (CI) is provided by GitHub Actions. For all pushes to the repository, GitHub Actions will:
install the requirements
run pre-commit hooks on all files
create the documentation to check for errors and warnings
only errors are checked for Windows
check for broken external links in the documentation
run tests and coverage
upload coverage reports to CodeCov
These “on push” CI checks are run on Ubuntu, macOS, and Windows operating systems, as
well as Python 3.6+. This Action can be found at workflows/govcookiecutter-build.yml
.
When a pull request is raised, GitHub Actions will also:
build an example project
navigate into the example project
initialise Git
install requirements
build the example project documentation, checking for errors but not warnings
check for broken external links in the documentation
run pre-commit hooks on all files
These “on pull request” CI checks are run on Ubuntu, and macOS operating systems, as well as Python 3.6+, and for example projects with or without R 4.0.4+.
Releases¶
Pull requests are raised on GitHub, and approved features are merged into main
. We
then use semantic versioning to number our releases. This helps our users
select a different version of govcookiecutter
to use based on their individual needs.