Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: leave previous answers intact when skipping question #1582

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion copier/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Main functions and classes, used to generate or update projects."""

from __future__ import annotations

import os
Expand Down Expand Up @@ -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:
Expand Down
17 changes: 12 additions & 5 deletions docs/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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: |-
Expand Down
49 changes: 49 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Loading