copier-org / copier

Library and command-line utility for rendering projects templates.
https://readthedocs.org/projects/copier/
MIT License
2.08k stars 183 forks source link

`placeholder` with no `default` does not force the user to answer #1753

Open JohnLyman opened 3 months ago

JohnLyman commented 3 months ago

Describe the problem

According to the docs, default should be left empty to force the user to answer. However, even with no default, if placeholder is set then the user is not forced to answer the prompt.

Template

# copier.yml
---
required_var:
  placeholder: foo

_message_after_copy: |
  required_var is {{ required_var }}

To Reproduce

Run copier with the copier.yml from the previous section. When prompted for required_var, simply hit enter.

Example:

copier copy -n . dir
🎤 required_var (yaml)
   foo

Copying from template version None
    create  .

required_var is None

Logs

No response

Expected behavior

When prompted, the user should be forced to enter a value and not be able to just hit enter, which effectively sets the value to None, "", etc. depending on the type.

Screenshots/screencasts/logs

No response

Operating system

macOS

Operating system distribution and version

Sonoma 14.6.1

Copier version

copier 9.3.1

Python version

Python 3.12.4

Installation method

pip+pypi

Additional context

This behavior can be worked-around with something like this:

validator: >-
  {% if not required_var %}
  INVALID - you must enter a value
  {% endif %}

... but that is a lot of boilerplate to add.

sisp commented 3 months ago

Interactive prompting is a little tricky because for type: str/type: yaml questions there is no clear distinction between no answer and an empty answer.

Let's assume the following copier.yml file:

str_1: ""

str_2:
  default: ""

str_3:
  type: str

All three questions are (implicit or explicit) type: str questions. When you enter no answer during interactive prompting, the answer will be an empty string because there is no way provide no answer (when you hit Enter, the answer is an empty string in all cases).

It's similar for type: yaml questions:

yaml_1: null

yaml_2:
  default: null

yaml_3:
  type: yaml

All three questions are (implicit or explicit) type: yaml questions. When you enter no answer during interactive prompting, the answer will be null because an empty string gets parsed as null by the YAML parser and there is no way to provide no answer (same as above).

So, your discovery is in fact not related to the use of placeholder with no default but rather the behavior of type: str/type: yaml questions with an implicit default value during interactive prompting.

I don't see how we could change the current behavior, but I'm all ears if you have an idea. Your proposed workaround using a validator does not precisely catch an unanswered question but constrains the answer to not be falsy, which coincidentally means it must not be "" or null.

JohnLyman commented 3 months ago

I've done some more testing, and I see what you are saying now. It works as I would expect for types other than yaml or str. I guess my confusion was mainly because I assumed "force the user to answer" also meant "with a non-empty response".

I don't have a better idea other than adding a new supported key for advanced prompts such as mandatory: False or allow_empty: True. I could see why you would be hesitant to do that though since the same could be achieved with validators.

I really liked the idea of placeholders to show example answers, but I can always hard-code examples in help where it makes sense.

Thanks

yajo commented 3 months ago

I think the MVP would be to document this behavior, and how validators can be used for this use case, so it's not surprising to users.