copier
copier copied to clipboard
Support hiding choices in a question
Actual Situation
I have a question with multiple choices which depend on a previous question and I would like to be able to hide some of the choices from the user. Consider this example copier.yaml file:
version:
type: str
help: Choose your Python version
choices:
- "3.8"
- "3.9"
image:
type: str
help: Choose a base Docker image
choices:
slim (python==3.8):
value: slim-3.8
validator: '{% if version == "3.8" %}{% else %}does not apply{% endif %}'
bullseye (python==3.8):
value: bullseye-3.8
validator: '{% if version == "3.8" %}{% else %}does not apply{% endif %}'
slim (python==3.9):
value: slim-3.9
validator: '{% if version == "3.9" %}{% else %}does not apply{% endif %}'
bullseye (python==3.9):
value: bullseye-3.9
validator: '{% if version == "3.9" %}{% else %}does not apply{% endif %}'
The user chooses a version, and depending on the version chosen, only some of the choices in the next answer will be 'selectable'. However, the 'unselectable' or 'disabled' choices in the second question are still visible to the user. In this toy example they are very similar to the 'enabled' choices and add clutter.
The situation isn't too bad with just a few different versions and images but you can imagine things would get very cluttered with more options.
Desired Situation
It would be nice to be able to have less clutter in the CLI prompt. Ideally, some way to be able to only show some choices, and hide others.
Proposed solution
One idea is to have another parameter, in addition to the validator and value parameters in the dict-style choices. Perhaps a parameter such as hidden which could be set to true and would then result in the choice being hidden from the user. E.g. something like
question:
type: str
help: Some help text
choices:
first:
value: some-value
validator: ...
hidden: true # could be dynamically set
...
Or perhaps a question-level parameter, e.g. hide_disabled, which could hide the choices which don't pass the validator. E.g.
question:
type: str
help: Some help text
hide_disabled: true
choices:
...
Thanks for your feature request, the detailed description and solution proposal. :pray:
Copier uses questionary for prompting. Questions with choices are mapped to questionary's select question type, and questions are marked as disabled by setting Choice(..., disabled="<message>").
I see two options to hide disabled questions:
-
Request this feature upstream in
questionary. Once available, Copier could use it. -
When a choice or question is configured to hide a disabled choice, we could insert something like
if disabled and value.get("hidden", False): continueafter
https://github.com/copier-org/copier/blob/5a5e445f0cff7356436af1d51031c3b3d8d0114d/copier/user_data.py#L328
If we introduce a question-level setting, we can internally propagate it down to each choice, so that hiding choices can always be handled there.
WDYT, @copier-org/maintainers?
Let me offer you a couple of solutions that currently work, just to see if you stillfeel the need for this feature after trying those.
The 1st one, obvious from the questionary you provided, is to just ask for the base distro:
version:
type: str
help: Choose your Python version
choices:
- "3.8"
- "3.9"
image:
type: str
help: Choose a base Docker image
choices:
- slim
- bullseye
You can do {{image}}-{{version}} to get the final result.
The second solution would be to see if it makes sense to split the selections in two questions. Each one of them can have a when clause, so you show one selection list or the other. Later in the template, you'd have to do a small gimnastic to get the value: {{ selection_1|d(selection_2)}} or similar.
Let me offer you a couple of solutions that currently work, just to see if you stillfeel the need for this feature after trying those.
The 1st one, obvious from the questionary you provided, is to just ask for the base distro:
version: type: str help: Choose your Python version choices: - "3.8" - "3.9" image: type: str help: Choose a base Docker image choices: - slim - bullseyeYou can do
{{image}}-{{version}}to get the final result.The second solution would be to see if it makes sense to split the selections in two questions. Each one of them can have a
whenclause, so you show one selection list or the other. Later in the template, you'd have to do a small gimnastic to get the value:{{ selection_1|d(selection_2)}}or similar.
Unfortunately the first solution doesn't quite work if there is an image that is supported by a different set of Python versions. Perhaps the toy example wasn't the best, but my use-case involves choices which have hard dependencies on the previous question.
So you could imagine that e.g. for version 3.8 there are slim and bullseye images, for 3.9 maybe there's only the bullseye image, for 3.10 there is slim and some other xyz image, etc. And once the user chooses the version, only a subset of all of the images should be shown.
As for the second approach, I don't think two questions would suffice in the general case. If I have n different versions and for each of them I want to show a different subset of images, would it still be possible to retrieve the value, e.g. {{ selection_1|d(selection_2|d(selection_3)|d(selection_4)...)}}? 😁
OK, yet another solution. Would it be OK if the disabled questions looked more different than the enabled ones? Current style seems too similar.
OK, yet another solution. Would it be OK if the disabled questions looked more different than the enabled ones? Current style seems too similar.
Yea, that could be a compromise if hiding the questions is too cumbersome to implement at the moment 😁