diff --git a/copier/main.py b/copier/main.py index 5ab475f57..4b5869009 100644 --- a/copier/main.py +++ b/copier/main.py @@ -1,4 +1,5 @@ """Main functions and classes, used to generate or update projects.""" + from __future__ import annotations import os @@ -440,7 +441,8 @@ def _ask(self) -> None: # Skip a question when the skip condition is met. if not question.get_when(): # Omit its answer from the answers file. - result.hide(var_name) + if var_name not in result.last: + result.hide(var_name) # Skip immediately to the next question when it has no default # value. if question.default is MISSING: diff --git a/docs/configuring.md b/docs/configuring.md index 91bcbd7f6..22e6b4040 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -176,17 +176,18 @@ Supported keys: will be rendered with the combined answers as variables; it should render _nothing_ if the value is valid, and an error message to show to the user otherwise. -- **when**: Condition that, if `false`, skips the question. +- **when**: Condition that, if `false`, skips the question and doesn't record an answer. - If it is a boolean, it is used directly. Setting it to `false` is useful for - creating a computed value. + If it is a boolean, it is used directly. - If it is a string, it is converted to boolean using a parser similar to YAML, but + If it is a string, it is converted to a boolean using a parser similar to YAML, but only for boolean values. The string can be [templated][prompt-templating]. - If a question is skipped, its answer is not recorded, but its default value is + If a question is skipped, its previous answer or default value is available in the render context. + Setting it to `false` together with default value is useful for creating a computed value. + !!! example ```yaml title="copier.yaml" @@ -199,6 +200,12 @@ Supported keys: - GPLv3 - Public domain + year_of_first_publication: + type: str + # ask when running `copier copy` + # skip this question when running `copier update` in the future + when: "{{ year_of_creation is not defined }}" + copyright_holder: type: str default: |- diff --git a/tests/test_config.py b/tests/test_config.py index a5f00f7c8..69f46c553 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,6 +13,7 @@ from copier.errors import InvalidConfigFileError, MultipleConfigFilesError from copier.template import DEFAULT_EXCLUDE, Task, Template, load_template_config from copier.types import AnyByStrDict +from copier.user_data import load_answersfile_data from .helpers import BRACKET_ENVOPS_JSON, SUFFIX_TMPL, build_file_tree @@ -614,3 +615,51 @@ def test_secret_question_requires_default_value( build_file_tree({src / "copier.yml": config}) with pytest.raises(ValueError, match="Secret question requires a default value"): copier.run_copy(str(src), dst) + + +def test_falsy_when_doesnt_modify_previous_answer( + tmp_path_factory: pytest.TempPathFactory, +) -> None: + src, dst = map(tmp_path_factory.mktemp, ("src", "dst")) + with local.cwd(src): + build_file_tree( + { + (src / "copier.yaml"): ( + """\ + initial_question: + type: str + default: "..." + when: "{{initial_question is not defined}}" + """ + ), + (src / "{{ _copier_conf.answers_file }}.jinja"): ( + """\ + # Changes here will be overwritten by Copier + {{ _copier_answers|to_nice_yaml }} + """ + ), + } + ) + git_init() + + copier.run_copy( + str(src), + dst, + defaults=True, + overwrite=True, + ) + + with local.cwd(dst): + git_init() + + answers = load_answersfile_data(dst) + assert "initial_question" in answers + + copier.run_update( + dst, + defaults=True, + overwrite=True, + ) + + answers = load_answersfile_data(dst) + assert "initial_question" in answers