shtab
shtab copied to clipboard
Give instructions to user with zero possible completions
A feature that jsonargparse+argcomplete supports is the print of help when there are zero possible completions. It would be great if the same feature can be implemented for shtab.
To explain what this feature does, the most simple case is an int argument. The value given to the argument could be already or not yet a valid int. In both cases nothing can be completed, only a message can be given to help the user. The following code snippet and behavior in the shell can illustrate this a bit better:
from typing import Dict
from jsonargparse import ArgumentParser
from jsonargparse.typing import PositiveFloat, Email
parser = ArgumentParser()
parser.add_argument('--int', type=int)
parser.add_argument('--dict', type=Dict[str, int])
parser.add_argument('--email', type=Email)
parser.add_argument('--positive', type=PositiveFloat)
parser.parse_args()
$ cli.py --int <TAB><TAB>
value not yet valid, expected type int
$ cli.py --int 1<TAB><TAB>
value already valid, expected type int
$ cli.py --int 1
$ cli.py --dict <TAB><TAB>
value not yet valid, expected type Dict[str, int]
$ cli.py --dict '{"v": <TAB><TAB>
value not yet valid, expected type Dict[str, int]
$ cli.py --dict '{"v": 1}'<TAB><TAB>
value already valid, expected type Dict[str, int]
$ cli.py --dict '{"v": 1}'
$ cli.py --email <TAB><TAB>
value not yet valid, expected type Email
$ cli.py --email abc@<TAB><TAB>
value not yet valid, expected type Email
$ cli.py --email [email protected]<TAB><TAB>
value already valid, expected type Email
$ cli.py --email [email protected]
$ cli.py --positive -1.5<TAB><TAB>
value not yet valid, expected type PositiveFloat
$ cli.py --positive 1.5<TAB><TAB>
value already valid, expected type PositiveFloat
$ cli.py --positive 1.5
Some details from when this was implemented for argcomplete can be seen in https://github.com/kislyuk/argcomplete/issues/328.
It is not clear to me how this could be implemented for shtab. Since there can be any number of data types that would have this behavior, this probably should be done with a completer that runs a python script. This is suggested in the last point in comment https://github.com/omni-us/jsonargparse/issues/108#issuecomment-986740522. However, neither the documentation of shtab or the customcomplete.py example make it clear how to do something like this.
It is not clear to me how this could be implemented for shtab. Since there can be any number of data types that would have this behavior, this probably should be done with a completer that runs a python script.
I think this might be a bit difficult, as it can be complicated to implement using native shell complements, and I haven't seen any examples calling scripts in other languages.
I don't think it is difficult. The documentation is missing how to implement a completer that runs a script. Having this clear, take for example that when completing it would run complete.py Email abc@
. The python script could print to stderr the message and send a signal to the shell to redraw the prompt. The complete.py call would return zero completions and shtab should not add anything. It is the same as what is being done for argcomplete.
Despite that, I really haven't seen any examples, at least in zsh completion, calling scripts in other languages as action, which involves how to tell the current command line state and then pass it back to the script, etc. It's hard for people who are not particularly familiar with each shell system completion to do that, which is probably why argcomplete only supports bash at the moment.
In addition, I think checking for legal variables on each input can be slow in a complex CLI, especially if I use a plugin like zsh-autocomplete that automatically generates completions. Speed is also a big problem for me when using argcomplete.
The motivation for this feature is precisely to allow implementing automatic generation of completions. In particular, automatic generation from type hints.
Also note that it is not strictly necessary to call another script for this feature to be useful. So there would be no issues regarding speed. For the example above the completions could be:
$ cli.py --int <TAB><TAB>
expected type int
$ cli.py --dict '{"v":<TAB><TAB>
expected type Dict[str, int]
$ cli.py --email [email protected]<TAB><TAB>
expected type Email
$ cli.py --positive -1.5<TAB><TAB>
expected type PositiveFloat
The shell specific logic should be part of shtab, such that people using it don't need to worry about these details.
How to tell shtab to do this could be like:
parser.add_argument('--int', type=int).complete = shtab.instructions("expected type int")
This would be if someone using argparse would want to do this. But my goal is that people using jsonargparse would have no need to specify .complete
attributes. They just add arguments normally with any type, or add a bunch of arguments from signatures. The completions would get added automatically.
Looks like 2 separate issues to me.
custom completion
Assuming you have this:
cli.py list-valid-ints # prints a finite (!) list of options
cli.py --int <int> # should tab-complete based on the above
Then see https://github.com/iterative/dvc/blob/main/dvc/cli/completion.py which has this pattern. The docs indirectly mention this (https://docs.iterative.ai/shtab/use/#library-usage -> https://github.com/iterative/dvc/blob/main/dvc/commands/completion.py). PRs/suggestions (modifying the docs source) to make it clearer are welcome.
type validation
However if you have this:
cli.py --int <int> # tab (rather than enter) should perform validation
I feel that's probably out of scope? Isn't the whole point of argparse.ArgumentParser.add_argument(type)
to perform type validation? Seems odd to offload this to shtab
.
I feel that's probably out of scope? Isn't the whole point of
argparse.ArgumentParser.add_argument(type)
to perform type validation? Seems odd to offload this toshtab
.
The point of a tool like shtab
is to develop ways to help users write commands, before the command is executed. How much to help depends on the developer of the CLI. What would be good is that shtab
provides several ways to help users. Using as example such a simple type as int
does not illustrate the need for it. But when a type is quite complex, some help that depends on what value for the argument has been written up to a point, surely could be welcome. So, I would say that it is not out of scope.
As I said in my previous comment, there is no need for value validation for the proposed feature to be useful. So this github issue can focus on giving static instructions with zero possible completions.
Then see https://github.com/iterative/dvc/blob/main/dvc/cli/completion.py which has this pattern. The docs indirectly mention this ...
Being required to implement shell specific preambles is not developer friendly and is a big amount of boilerplate if a similar feature is to be added to different projects. What this github issue proposes is to add to shtab
a simple way to configure this.
Regarding the documentation, it is quite far away from explaining about preambles and shell specific code. Regardless, I don't think this is about improving documentation of the current shtab
features.
cli.py list-valid-ints # prints a finite (!) list of options cli.py --int <int> # should tab-complete based on the above
It is not clear to me what is the behavior with this. Let me give a different example. The behavior can be shell dependent, so assume the following is for bash. Assume an argument is added like
action = parser.add_argument('--lottery-numbers', type=int, nargs=5)
Note that there is no parser validation regarding the range of numbers. And since they are numbers, there is nothing that can be completed, i.e. the command written by the user won't be extended. With the proposed feature, a developer could do
action.complete = shtab.instructions("give five numbers between 1 and 56")
The tab behavior would be the following.
$ cli.py --lottery-numbers <TAB><TAB>
give five numbers between 1 and 56
$ cli.py --lottery-numbers 1 2<TAB><TAB>
give five numbers between 1 and 56
$ cli.py --lottery-numbers 1 2 3 4 5 6 7<TAB><TAB>
give five numbers between 1 and 56
$ cli.py --lottery-numbers 1 2 3 4 5 6 7<CURSOR>
Note that the different prompts $
are not different examples. This is how the terminal would look like. After a <TAB><TAB>
, the instructions are printed and the prompt is redrawn with the same text the user had written. Then the user writes more and again does <TAB><TAB>
. In this example first no value is written, then 1 2
, then adds 3 4 5 6 7
. On purpose I added more than 5 numbers because there would be no validation before running the command.
A final note. Have in mind what I wrote at the end of the description in https://github.com/iterative/shtab/issues/68. My idea is that later on the complete
attribute could support being a list. For example, be possible something like
action = parser.add_argument("--style", type=str)
action.complete = [
shtab.path_completer(["*.jpg"]),
shtab.instructions("path to the image with the target style to apply"),
]
In this case <TAB><TAB>
would print the instructions and the paths that match, redraw the prompt and if possible extend the command with a common prefix, e.g. the user wrote target
and all images are like target_style_*.jpg
, so _style_
would be appended.