-
-
Notifications
You must be signed in to change notification settings - Fork 7
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
[Feature Request] Completion? #89
Comments
I would love to add autocompletion, but have no experience in the area. I would certainly welcome any input, whether it be general recommendations, library suggestions, PRs, etc. |
Just a heads up, I am working on this, hoping to have something testable in the next week or two. |
shell completion is infinitely more complex than I was initially expecting 😄 . Still a WIP. |
This is somewhat of a deal breaker for me so I'm keen to help out to add this feature! |
Currently I'm working on adding bash and zsh support; i'm taking another direction on it and going to be basing the code off of shtab's implementation, which I think is a good reference. Originally I was pursuing the idea of adding functionality to generate an |
I'm still thinking about this; I'm actually leaning towards the argparse solution (and maybe even also having a |
Hi @BrianPugh I've recently discovered cyclopts after becoming disillusioned with typer's slow (possibly stalled) development I love everything you've done with it so far, and the only thing preventing me from adopting it in all my projects is the lack of shell completion. I have users leveraging shell completion in typer now, and taking that away from them is a hard no I'm wondering if you'd be willing to publish your WIP on shell completion in another branch so that I or others could take a look at it and possibly help you finish it? |
nvm, I just realized you already did that! hahaha looking at the code, i think i see what you were going for. I agree that some kind of parser transformer (converting and |
Hey @alextremblay! I totally agree that shell completion should be the next important feature to implement I'm actually working on a cyclopts v3.0.0 that does a fairly large revamp of all the internal structures. The big feature of v3.0.0 is that it will support dictionary-like parameters (dicts, typeddicts, dataclasses, attrs, pydantic, etc). Error messages will also be much more helpful because exact user-provided values and their source (CLI, config files, environment variables, etc) are now explicitly logged. Some of the lesser used cyclopts functionality will have a slight API change, but I don't think it'll impact most users. In cyclopts v3.0.0 a lot more of the argument parsing/resolving is done up-front, which should make implementing shell-completion much easier. My current branch I'm working on is here. It's not functional yet, and a lot of the commits are sloppy due to the large changes. It's also going to change a lot, but you can track my progress a bit over there. My initial attempts were converting cyclopts structures to something like ArgumentParser objects, but there are some incompatibilities that make it not work, so I sort of abandoned that idea. Benefits of that approach is that it could increase inter-operability with other CLI libraries. However, for now, I'll just be implementing direct shell completion without intermediate conversion. I've been diligently working on v3.0.0, but no guarantees on a timeframe. I hope to have it done in about a month (and then start working on shell completion again), but you never know if life gets in the way 😄 . |
That’s awesome! Another option you may want to consider regarding shell completion, outsourcing! The downside of using carapace-bin instead of doing your own shell completion Is that all users who want shell completion would have to install carapace (more on that later)
The core idea here is that we could pre-generate completion specs for all the parts of our cyclopts command tree (including subcommands, options, positional args) which are known at compile-time, and compile them into an installable carapace-spec. For parts of the cyclopts CLI that are too dynamic to pregenerate, we have two options:
We could also provide a mechanism for users to install carapace and install our CLI spec file i guess all of this is to say: if you like this idea, I’d like to work with you to implement carapace-bin completion in cyclopts. Once the v3 codebase is semi-stable (to the point where the data structures involved are mostly stable), I can start working on a program that takes those data structures and generates a carapace Spec file. Thoughts? |
I like the idea! I'll post back in this thread in a week or two with a more meaningful follow up (with a hopefully solidified v3). I would love help on this! |
@BrianPugh maybe shtab's developers would be interested in incorporating positional-or-keyword arguments? I feel like shtab is feature-rich, focused and not bloated. cyclopts api + autosuggestion would be awesome. cc @casperdcl |
What's a "positional-or-keyword" arg? |
Hey @arogozhnikov @casperdcl ! Big fan of shtab, I was actually going to base Cyclopt's autocompletion on it; the codebase is very clean and minimal. Is there an API that Cyclopts could use (or vise-versa) to hook into shtab directly?
Exactly. Something like: @app.default
def foo(src, dst):
print(f"Copying {src} -> {dst}") Has the following equivalent CLI invocations: python my-script.py fizz buzz
python my-script.py --src fizz buzz
python my-script.py --src fizz --dst buzz
python my-script.py --dst buzz --src fizz
python my-script.py --dst=buzz fizz |
I... don't think from argparse import ArgumentParser
parser = ArgumentParser(prog="test")
parser.add_argument('arg', default='pos', nargs='?')
parser.add_argument('--arg', default='opt')
parser.parse_args() # ignores --arg Though the two cases will exist in the |
Correct, |
Just tested; the #!/usr/bin/env python
#test
from argparse import ArgumentParser
import shtab
parser = ArgumentParser(prog="test")
key = 'arg'
vals = 'foo bar baz'.split()
parser.add_argument(key, choices=vals, nargs='?')
parser.add_argument(f'--{key}', choices=vals)
shtab.add_argument_to(parser)
parser.parse_args() eval "$(test --print-completion bash)"
test <TAB> # foo bar baz
test f<TAB> # foo
test -<TAB> # --arg
test --arg <TAB> # foo bar baz
test --arg foo <TAB> # foo bar baz that last line ambiguity btw is probably why I don't think |
I also found this counterintuitive: python doesn't allow such calls; and when refactoring code I'm thinking about which python calls can be/should be broken when I change function signature, and this CLI call wouldn't cross my mind. |
So this is an excellent time to discuss this, because if we're going to make a breaking change, it's best to do it with the upcoming v3. As a thought experiment, let's partially implement the cli tool cp -r source_path destination_path
cp source_path destination_path -r
cp source_path --dst destination_path -r
cp --src source_path --dst destination_path -r
cp --dst destination_path --src source_path -r The following definitely seems wonky and unintuitive: cp -r --dst destination_path source_path
cp --dst destination_path -r source_path
cp --src source_path destination_path -r A reasonable python function signature would look like: @app.command
def cp(src, dst, recursive: Annotated[bool, Parameter(name="-r")]):
... To match python-syntax, we could simply say that positional-arguments cannot come after keyword arguments. However, in CLI it's quite frequent to front-load the command with a bunch of flags/keywords with the positional arguments trailing. It's also common to put all the flags/keywords after the positional arguments (at the end). It is NOT common to inter-mingle the two. So, a first attempt at fixing this could be the following ruleset (using
I am tempted to add a fourth rule: "Boolean So, for our example, the function signature should actually be: @app.command
def cp(src, dst, *, recursive: Annotated[bool, Parameter(name="-r")]):
... Thoughts/Feedback on the above ruleset? |
I think equivalent of your procedure would be:
I likely oversimplify, I just want some very simple 'visual rule' how CLI args convert to function inputs. |
seems as borked as: def foo(src, dst):
...
foo(src="fizz", "buzz")
foo("buzz", src="fizz")
I disagree with your implementation. AFAIK def cp(*source, target_directory=None, no_target_directory=False, recursive=False):
if target_directory is None:
source, target_directory = source[:-1], source[-1]
dest = target_directory if no_target_directory else None
assert xor(dest, target_directory)
copy = getattr(shutil, 'copytree' if recursive else 'copy2')
cp("foo", "bar")
cp("foo", target_directory="bar") cp foo bar
cp foo --target-directory=bar Meanwhile all of the following raise errors: cp("bar", source="foo")
cp("bar", source=("foo",))
cp(source="foo", target_directory="bar")
cp(source=("foo",), target_directory="bar") cp --source foo bar
cp --source foo --target-directory=bar |
Exactly, which is why I'm proposing to change how the arguments are parsed to disallow these unusual/unintuitive situations.
It was meant to be a simple example showing intermingling of flags and positional-or-keyword parameters.
And I agree, I think all of those should be disallowed. |
@arogozhnikov They're similar, but we have to parse keywords first due to nuances with the number of tokens that get associated with each python parameter. Consider the following: def move(x:int , y: int, *, origin: tuple[int, int] | None = None):
"""Move player location.""" move --origin 3 4 1 2
# Parsed as:
origin=(3,4)
x=1
y=2 We first have to parse EDIT: I may have mis-interpretted your comment; I think we're all saying the same thing. |
fyi |
Typer/Click works the same as Cyclopts here (consuming enough tokens to populate the tuple). I think it's the more intuitive option, and also allows you to do list of tuples like: @app.default
def main(coordinates: List[Tuple[int, int]]):
... python my-script.py --coordinates 1 2 --coordinates 3 4 |
Curious if this project currently has completion (couldn't find it in the docs or code) or if there are aspirations to provide completion in the future??
The text was updated successfully, but these errors were encountered: