Reputation: 10862
I am using python click to implement a cli. Simplest example would allow me to say:
./mytest.py help
but also
./mytest.py h
In other words, the help
command can be shortened to just h
. This is the way many cli programs work, e.g. git
.
From what I can tell the code snippet:
@click.command()
def help():
click.echo("Help!)
Will allow me to have:
./mytest.py help
But I don't know how to allow the synonym. I can't find anything that explains this.
Upvotes: 3
Views: 753
Reputation: 4455
I use the context forward method.
$ cat cli.py
#!/usr/bin/env python
import click
@click.group()
def cli():
"""
cli commands
"""
@cli.command()
def help():
"""
Get help
"""
click.echo("Help!")
@cli.command("h")
@click.pass_context
def another_help(ctx):
"""
Another way of asking for help
"""
ctx.forward(help)
if __name__ == "__main__":
cli()
And here's a test run:
$ ./cli.py
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
cli commands
Options:
--help Show this message and exit.
Commands:
h Another way of asking for help
help Get help
$ ./cli.py h
Help!
$ ./cli.py help
Help!
For whatever reason, I also demonstrated that the command name and function name can be different: I named the synonym function another_help
but I told click the command is h
.
The real issue with my approach is that the help text associated with the synonym must be copied, too. More importantly, the parameters (that is, options and arguments) also have to be copied.
In essence, the ctx.forward()
approach increases the maintenance burden. It is easy to get them out of sync (let's say as you added additional options or arguments).
As you can see in the test run, the same command also gets listed twice. And so, I often just hide the synonym so that the list of commands is clearer. Plus, I don't have to keep the help text (Another way of asking for help) in sync.
So, here's an improved version, with the h
synonym hidden:
$ cat cli.py
#!/usr/bin/env python
import click
@click.group()
def cli():
"""
cli commands
"""
@cli.command()
def help():
"""
Get help
"""
click.echo("Help!")
@cli.command("h",hidden=True)
@click.pass_context
def another_help(ctx):
"""
Another way of asking for help
"""
ctx.forward(help)
if __name__ == "__main__":
cli()
$ ./cli.py
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
cli commands
Options:
--help Show this message and exit.
Commands:
help Get help
$ ./cli.py h
Help!
Finally, here's the "parameter problem": if you add a parameter to one function, you have to add the same parameter to the synonym.
In the example below, I added a --name
option to the help
command, but not the h
synonym:
$ cat cli.py
#!/usr/bin/env python
import click
@click.group()
def cli():
"""
cli commands
"""
@cli.command()
def help():
"""
Get help
"""
click.echo("Help!")
@cli.command("h",hidden=True)
@click.pass_context
def another_help(ctx):
"""
Another way of asking for help
"""
ctx.forward(help)
if __name__ == "__main__":
cli()
$ vi cli.py
$ ./cli.py
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
cli commands
Options:
--help Show this message and exit.
Commands:
help Get help
$ ./cli.py help Fred
Help, Fred!
$ ./cli.py h Fred
Usage: cli.py h [OPTIONS]
Try 'cli.py h --help' for help.
Error: Got unexpected extra argument (Fred)
Above, you can see that the h
command has a problem with the argument. And so, to correct this, I would also have to add name to the another_help
function like this:
@cli.command("h",hidden=True)
@click.pass_context
@click.argument("name",required=False)
def another_help(ctx,name):
"""
Another way of asking for help
"""
ctx.forward(help)
And that's what I mean about the maintenance burden - adding parameters or options has to be done to both synonyms.
Upvotes: 1
Reputation: 21901
In the Click documentation, this is known as "aliases".
The documentation at https://click.palletsprojects.com/en/8.x/advanced/ provides a code snippet for what you're looking for. Additionally, it links to sample code at https://github.com/pallets/click/tree/master/examples/aliases.
Here is a minimal implementation for the example given:
import click
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
if cmd_name in ["help", "h"]:
return click.Group.get_command(self, ctx, "help")
return None
@click.command(cls=AliasedGroup)
def cli():
pass
@cli.command()
def help():
click.echo("Help!")
if __name__ == "__main__":
cli()
Upvotes: 2