Click: Allow a type to customize the prompt

Created on 20 Oct 2019  路  8Comments  路  Source: pallets/click

As of Click 7, click.Choice appears only to support choice prompting by string, i.e. the user must specify the exact string (modulo case sensitivity) from the choices. It would be nice if Click could support two additional modes of selection:

Numeric:

Choose from:
[1] Choice A...
[2] Choice B...
[3] Choice C...
Enter the index for one of the above choices: 2
"Choice B..." was selected

or partial matches as long as what the user enters is unique:

Choose from:
This thing
That thing
Those things
Enter choice: thi
"This thing" was selected

("thi" is unique amongst the three strings so is chosen)

I thought it would be easy to implement this by subclassing click.Choice but unfortunately not: click.Choice does not provide the initial prompt message (only the prompt after an invalid choice) - this is provided by click.prompt from the specified choices list. The prompt message in at least the first example above would require to be changed to add numeric indexes. It seems to me therefore that this functionality requires more structural changes to click and so I thought I'd submit this issue.

prompt

Most helpful comment

Selecting the choice would be much nicer. Like this: https://github.com/Mckinsey666/bullet#bullet-lists-and-checkboxes

All 8 comments

I implemented numeric choices as a subclass of click.Choice:

class NumericChoice(click.Choice):
    def __init__(self, choices, **kwargs):
        choicepairs = []
        choicestrs = []
        for i, choice in enumerate(choices, start=1):
            choicepairs.append((str(i), choice))
            choicestrs.append("[{}] {}".format(i, choice))
        self.choicemap = dict(choicepairs)
        super().__init__(choicestrs, **kwargs)

    def convert(self, value, param, ctx):
        try:
            return self.choicemap[value]
        except KeyError as e:
            self.fail('invalid choice: %s. (choose from %s)' %
                      (value, ', '.join(self.choices)), param, ctx)

It works for me, but I'm targeting Python 3.6+ only. If this might be useful I can clean it up and make a pull request at some point. It might be best to add this as an option to the existing click.Choice, though.

For partial string matching, perhaps difflib would be useful. I haven't thought how to do that yet.

@SeanDS in addition to (or possibly instead of) partial string matching, supporting completion on the click.Choice types is arguably even better; though I'm not quite sure how that would be implemented either

Selecting the choice would be much nicer. Like this: https://github.com/Mckinsey666/bullet#bullet-lists-and-checkboxes

Selecting the choice would be much nicer. Like this: https://github.com/Mckinsey666/bullet#bullet-lists-and-checkboxes

I disagree. For mutually exclusive choices, selection by index or prefix is quicker in general, despite being "less pretty".

Selecting the choice would be much nicer. Like this: https://github.com/Mckinsey666/bullet#bullet-lists-and-checkboxes

I disagree. For mutually exclusive choices, selection by index or prefix is quicker in general, despite being "less pretty".

I think selection by index or prefix is not great for those reasons :

  • For small lists, it's not really faster than selection with arrows.
  • It's more error prone. Especially, if the CLI you are using is updated by the author and items are added. If, for example, you usually use choice 8, because an item has been added it's now choice 9. You might select choice 8 by memory without realizing it's not correct anymore.
  • For long lists, it might not be possible to display all options on the screen. You would have to implement some scrolling of some sort. You might as well let people select directly instead of making them scroll, remeber the index etc.
  • For multiple choices, it gets quite weird to specify multiple index, and it's not obvious what to do. Do you need space between indexes ? Commas ?
  • A selection by arrows and highlighting, can be later customized to support filtering etc. With indexes, what happens when you have a long list and you need to filter ? Do you re-index ? Do you keep indexes ? In the later case it's not pretty to have to type 278 for an item you already filtered.

I think the style of https://github.com/bchao1/bullet#bullet-lists-and-checkboxes makes more sense.

For typical lists for which I'd use these methods of selection (n <= 15), selection by index requires less key strokes. Of course, shorter the list, smaller the advantage.

It's more error prone.

Because it's quicker and the index is easily memorizable (which is an advantage). It's a tradeoff. Note that there's no problem if either:

  • the developer appends new choices to the list, preserving indexes
  • the user is smart and uses CLI aliases or a configuration file instead of typing the same option interactively so many times that he memorizes the indexes :).

For long lists, it might not be possible to display all options on the screen.

For long lists, both methods (in their "plain" version) are terrible. It's actually the worst-case scenario for selection by arrows. You want filtering/completion for long lists.

For multiple choices, it gets quite weird to specify multiple index

Absolutely agree. Indeed I specified "mutually exclusive choices" in my previous comment.

I'm +1 on making it possible for a type to customize what is shown in the prompt, although I don't plan to modify how Click's built-in Choice type works any further. That way, users can make a choice type with whatever matching behavior they want.

I'm +1 on making it possible for a type to customize what is shown in the prompt, although I don't plan to modify how Click's built-in Choice type works any further. That way, users can make a choice type with whatever matching behavior they want.

That's fair enough, the lack of ability to customise the message was what I missed most in the original issue.

Was this page helpful?
0 / 5 - 0 ratings