diff --git a/copier.yaml b/copier.yaml index d404f8f..c841a71 100644 --- a/copier.yaml +++ b/copier.yaml @@ -38,45 +38,92 @@ _templates_suffix: .jinja _answers_file: .copier-answers.yml # === copier user provided === -module_short_name: +module_code_name: type: str - help: Please enter your module's short name as used in snakemake (should be in `snake_case`). This will be folder name within which your module lives. + help: | + Please enter your module's name as used in code in `snake_case`. This will be used for GitHub and Snakemake access. + 💡 Core modules should start with "module_" as a convention. + default: module_ validator: >- - {% if not (module_short_name | regex_search('^[a-z][a-z0-9_]*$')) %} + {% if not (module_code_name | regex_search('^[a-z][a-z0-9_]*$')) %} "Only lowercase letters, digits and underscores are valid." {% endif %} -module_long_name: +module_human_name: type: str - help: Please enter your module's long name (e.g., PV capacity factors, Hydropower, Heat-pump profiles, etc). + help: | + Please enter your module's human readable name (e.g., PV capacity factors, Hydropower, Heat-pump profiles, etc). + 💡 This name will be used in the Modelblocks website, citation files and README documentation. module_description: type: str - help: "Please provide a description of your module." -author_given_name: - type: str - help: "Please provide your given name (e.g., Ursula)." -author_family_name: - type: str - help: "Please provide your family name (e.g., Le Guin)." -author_email: - type: str - help: Please provide your email address. + help: | + Please provide a description of your module. + 💡 These will be used as the README's introduction. +authors: + type: yaml + multiline: true + help: | + Please provide a list of module authors (i.e., the people that wrote the software). + 💡 These will be listed in licensing files and referenced when citing the software. + 💡 Each author needs a given_name, family_name, and email fields. + 💡 Multiple authors are possible (separate them with '-'). + default: + - given_name: Ursula + family_name: Le Guin + email: ursula.leguin@example.com validator: >- - {% if not (author_email | regex_search('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.][a-zA-Z0-9-.]+$')) %} - "Invalid email address." + {% if authors is not sequence or authors is string or authors | length == 0 %} + Please provide at least one author as a YAML list. + {% else %} + {% for author in authors %} + {% if author is not mapping %} + Author {{ loop.index }} must be a mapping with given_name, family_name, and email. + {% elif not author.given_name or not author.family_name or not author.email %} + Author {{ loop.index }} must include given_name, family_name, and email. + {% elif not (author.email | regex_search('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.][a-zA-Z0-9-.]+$')) %} + Invalid email address for author {{ loop.index }}. + {% endif %} + {% endfor %} + {% endif %} +maintainers: + type: yaml + multiline: true + help: | + Please provide the module maintainers (i.e., the people currently responsible for the software). + 💡 Similar to authors, each needs a given_name, family_name, and email. + 💡 The same person can be both an author and a maintainer. + default: + - given_name: Juan + family_name: Rulfo + email: j.rulfo@example.com + validator: >- + {% if maintainers is not sequence or maintainers is string or maintainers | length == 0 %} + Please provide at least one maintainer as a YAML list. + {% else %} + {% for maintainer in maintainers %} + {% if maintainer is not mapping %} + Maintainer {{ loop.index }} must be a mapping with given_name, family_name, and email. + {% elif not maintainer.given_name or not maintainer.family_name or not maintainer.email %} + Maintainer {{ loop.index }} must include given_name, family_name, and email. + {% elif not (maintainer.email | regex_search('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+[.][a-zA-Z0-9-.]+$')) %} + Invalid email address for maintainer {{ loop.index }}. + {% endif %} + {% endfor %} {% endif %} github_org: type: str - help: >- - Please provide the name of the github account or organisation where - this module will be created (e.g., 'modelblocks-org' in 'github.com/modelblocks-org'). + help: | + Please provide the name of the github account or organisation where this module will be created. + 💡 For example: 'modelblocks-org' in 'github.com/modelblocks-org'. + default: "modelblocks-org" validator: >- {% if not (github_org | regex_search('^[a-zA-Z0-9][a-zA-Z0-9-]*$')) %} "Only lowercase letters, digits, hyphens and underscores are valid." {% endif %} - default: "modelblocks-org" license: type: str - help: "Please choose a license." + help: | + Please choose a license. + 💡 Core modules must use Apache-2.0 as a standard. choices: - "Apache-2.0" - "MIT" diff --git a/template/.all-contributorsrc.jinja b/template/.all-contributorsrc.jinja index 16e4443..3ac1bdf 100644 --- a/template/.all-contributorsrc.jinja +++ b/template/.all-contributorsrc.jinja @@ -1,5 +1,5 @@ { - "projectName": "{{module_short_name}}", + "projectName": "{{module_code_name}}", "projectOwner": "{{github_org}}", "repoType": "github", "repoHost": "https://github.com", diff --git a/template/AUTHORS.jinja b/template/AUTHORS.jinja index 400f351..fa5f5f2 100644 --- a/template/AUTHORS.jinja +++ b/template/AUTHORS.jinja @@ -2,6 +2,8 @@ This is the list of contributors for copyright purposes. This does not necessarily list everyone who has contributed to this software's code or documentation. For a full contributor list, see: - + + -{{author_given_name}} {{author_family_name}}, <{{author_email}}> +{% for author in authors %}{{ author.given_name }} {{ author.family_name }}, <{{ author.email }}> +{% endfor %} diff --git a/template/CITATION.cff.jinja b/template/CITATION.cff.jinja index 0e1de2c..eeb9e09 100644 --- a/template/CITATION.cff.jinja +++ b/template/CITATION.cff.jinja @@ -3,9 +3,11 @@ # https://citation-file-format.github.io/ cff-version: 1.2.0 message: If you use this software or data produced by it, please cite it using the metadata from this file. -title: "Modelblocks - {{module_short_name}}: {{module_long_name}}" -repository: "https://github.com/{{github_org}}/{{module_short_name}}" +title: "Modelblocks - {{module_code_name}}: {{module_human_name}}" +repository: "https://github.com/{{github_org}}/{{module_code_name}}" license: {{license}} authors: - - given-names: {{author_given_name}} - family-names: {{author_family_name}} +{% for author in authors %} - given-names: {{ author.given_name | tojson }} + family-names: {{ author.family_name | tojson }} + email: {{ author.email | tojson }} +{% endfor %} diff --git a/template/README.md.jinja b/template/README.md.jinja index d6cb8fa..b21af00 100644 --- a/template/README.md.jinja +++ b/template/README.md.jinja @@ -1,8 +1,11 @@ -# {{module_long_name}} +# {{module_human_name}} {{module_description}} +

+ +

## About @@ -39,8 +42,8 @@ We use [`pixi`](https://pixi.sh/) as our package manager for development. Once installed, run the following to clone this repository and install all dependencies. ```shell -git clone git@github.com:{{github_org}}/{{module_short_name}}.git -cd {{module_short_name}} +git clone git@github.com:{{github_org}}/{{module_code_name}}.git +cd {{module_code_name}} pixi install --all ``` diff --git a/template/figures/module.png b/template/figures/module.png new file mode 100644 index 0000000..1a83735 Binary files /dev/null and b/template/figures/module.png differ diff --git a/template/pixi.toml.jinja b/template/pixi.toml.jinja index 64f4a12..a2e14a3 100644 --- a/template/pixi.toml.jinja +++ b/template/pixi.toml.jinja @@ -1,5 +1,5 @@ [workspace] -name = "{{ module_short_name }}" +name = "{{ module_code_name }}" authors = ["See AUTHORS file"] description = "{{ module_description }}" license = "{{ license }}" diff --git a/template/tests/integration/test_config.yaml.jinja b/template/tests/integration/test_config.yaml.jinja index 0ac4327..df6d354 100644 --- a/template/tests/integration/test_config.yaml.jinja +++ b/template/tests/integration/test_config.yaml.jinja @@ -1,4 +1,4 @@ -{{module_short_name}}: +{{module_code_name}}: dummy_text: >- Configuration values (like this one) are also external to the module! This gives users a lot of flexibility in how they apply your methodology to solve their particular needs. diff --git a/template/tests/integration/{% if true %}Snakefile{% endif %}.jinja b/template/tests/integration/{% if true %}Snakefile{% endif %}.jinja index 1317149..617c374 100644 --- a/template/tests/integration/{% if true %}Snakefile{% endif %}.jinja +++ b/template/tests/integration/{% if true %}Snakefile{% endif %}.jinja @@ -21,9 +21,9 @@ rule create_external_input: # `snakefile:` specifies the module. It can use file paths and special github(...) / gitlab(...) markers # `config`: specifies the module configuration. # `pathvars:` helps you re-wire where the module places files. -module {{module_short_name}}: +module {{module_code_name}}: snakefile: "../../workflow/Snakefile" - config: config["{{module_short_name}}"] + config: config["{{module_code_name}}"] pathvars: # Redirect specific user resources (inputs) user_message=rules.create_external_input.output.text_file, @@ -36,7 +36,7 @@ module {{module_short_name}}: # rename all module rules with a prefix, to avoid naming conflicts. -use rule * from {{module_short_name}} as {{module_short_name}}_* +use rule * from {{module_code_name}} as {{module_code_name}}_* # Request something from the module rule all: diff --git a/template/{% if license == 'Apache-2.0' %}LICENSE{% endif %}.jinja b/template/{% if license == 'Apache-2.0' %}LICENSE{% endif %}.jinja index 9205df5..261eeb9 100644 --- a/template/{% if license == 'Apache-2.0' %}LICENSE{% endif %}.jinja +++ b/template/{% if license == 'Apache-2.0' %}LICENSE{% endif %}.jinja @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {{ '%Y' | strftime }} AUTHORS + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/tests/template_test.py b/tests/template_test.py index 67b502b..18cb4f9 100644 --- a/tests/template_test.py +++ b/tests/template_test.py @@ -5,9 +5,83 @@ from pathlib import Path import pytest +import yaml from copier import run_copy +class TestTemplateAuthors: + """Build and check author metadata rendering.""" + + def test_multiple_authors_are_rendered( + self, tmp_path, template_path, simple_answers + ): + """Multiple author answers should render in metadata files.""" + answers = deepcopy(simple_answers) + answers["authors"] = [ + { + "given_name": "Ursula", + "family_name": "Le Guin", + "email": "ursula.leguin@example.com", + }, + { + "given_name": "Stanislaw", + "family_name": "Lem", + "email": "s.lem@example.com", + }, + ] + + run_copy( + src_path=str(template_path), + dst_path=str(tmp_path), + data=answers, + vcs_ref="HEAD", + ) + + copier_answers = yaml.safe_load((tmp_path / ".copier-answers.yml").read_text()) + assert copier_answers["authors"] == answers["authors"] + + authors = (tmp_path / "AUTHORS").read_text() + assert "Ursula Le Guin, " in authors + assert "Stanislaw Lem, " in authors + + citation = (tmp_path / "CITATION.cff").read_text() + assert 'given-names: "Ursula"' in citation + assert 'family-names: "Lem"' in citation + assert 'email: "s.lem@example.com"' in citation + + +class TestTemplateMaintainers: + """Build and check maintainer answer rendering.""" + + def test_multiple_maintainers_are_stored( + self, tmp_path, template_path, simple_answers + ): + """Multiple maintainer answers should be stored for documentation.""" + answers = deepcopy(simple_answers) + answers["maintainers"] = [ + { + "given_name": "Juan", + "family_name": "Rulfo", + "email": "j.rulfo@example.com", + }, + { + "given_name": "Toni", + "family_name": "Morrison", + "email": "toni.morrison@example.com", + }, + ] + + run_copy( + src_path=str(template_path), + dst_path=str(tmp_path), + data=answers, + vcs_ref="HEAD", + ) + + copier_answers = yaml.safe_load((tmp_path / ".copier-answers.yml").read_text()) + assert copier_answers["maintainers"] == answers["maintainers"] + + class TestBuiltTemplate: """Build and check the resulting template.""" diff --git a/tests/utils/simple_answers.yaml b/tests/utils/simple_answers.yaml index aab13c1..f045241 100644 --- a/tests/utils/simple_answers.yaml +++ b/tests/utils/simple_answers.yaml @@ -1,8 +1,13 @@ -module_short_name: test_module -module_long_name: Test module +module_code_name: test_module +module_human_name: Test module module_description: A simple module to test the template -author_given_name: Tester -author_family_name: McTest -author_email: tester.mctest@example.com +authors: + - given_name: Tester + family_name: McTest + email: tester.mctest@example.com +maintainers: + - given_name: Maintain + family_name: McMaintainer + email: maintain.mcmaintainer@example.com github_org: modelblocks-org -license: +license: "Apache-2.0"