Click absolutely supports this sort of syntax. A simple example looks something like:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
def main(common_option1, common_option2):
pass
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
pass
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
pass
@main.command()
def cmd3():
pass
if __name__ == '__main__':
main()
Assuming the above is in mycliapp.py
, we see the common help output:
$ python example.py --help
Usage: example.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...
Options:
--common-option1 TEXT
--common-option2 TEXT
--help Show this message and exit.
Commands:
cmd1
cmd2
cmd3
And for cmd1
:
$ python mycliapp.py cmd1 --help
Usage: mycliapp.py cmd1 [OPTIONS]
Options:
--cmd1-option
--help Show this message and exit.
And for cmd2
:
$ python mycliapp.py cmd2 --help
Usage: mycliapp.py cmd2 [OPTIONS]
Options:
--cmd2-option TEXT
--help Show this message and exit.
Etc.
With this we can run the command line from your question:
python mycliapp.py --common-option1 value1 --common-option2 value2 \
cmd1 --cmd1-option \
cmd2 --cmd2-option somevalue \
cmd3
Update 1
Here's an example that implements pipelines using the callback model suggested in the documentation:
import click
@click.group(chain=True)
@click.option('--common-option1')
@click.option('--common-option2')
@click.pass_context
def main(ctx, common_option1, common_option2):
ctx.obj = {
'common_option1': common_option1,
'common_option2': common_option2,
}
@main.resultcallback()
def process_pipeline(processors, common_option1, common_option2):
print('common_option1 is', common_option1)
for func in processors:
res = func()
if not res:
raise click.ClickException('Failed processing!')
@main.command()
@click.option('--cmd1-option', is_flag=True)
def cmd1(cmd1_option):
def process():
print('This is cmd1')
return cmd1_option
return process
@main.command()
@click.option('--cmd2-option')
def cmd2(cmd2_option):
def process():
print('This is cmd2')
return cmd2_option != 'fail'
return process
@main.command()
@click.pass_context
def cmd3(ctx):
def process():
print('This is cmd3 (common option 1 is: {common_option1}'.format(**ctx.obj))
return True
return process
if __name__ == '__main__':
main()
Each command returns a boolean indicating whether or not it was successful. A failed command will abort pipeline processing. For example, here cmd1
fails so cmd2
never executes:
$ python mycliapp.py cmd1 cmd2
This is cmd1
Error: Failed processing!
But if we make cmd1
happy, it works:
$ python mycliapp.py cmd1 --cmd1-option cmd2
This is cmd1
This is cmd2
And similarly, compare this:
$ python mycliapp.py cmd1 --cmd1-option cmd2 --cmd2-option fail cmd3
This is cmd1
This is cmd2
Error: Failed processing!
With this:
$ python mycliapp.py cmd1 --cmd1-option cmd2 cmd3
This is cmd1
This is cmd2
This is cmd3
And of course you don't need to call things in order:
$ python mycliapp.py cmd2 cmd1 --cmd1-option
This is cmd2
This is cmd1