Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alias for a chain of commands

I have a tool with commands: step1, step2 and step3.

I can chain them by calling:

$ tool step1 step2 step3

I would like to have an alias named all to run all the steps by calling:

$ tool all

I have found a solution that works but it doesn't seem right for me because of calling cli() twice under the hood:

@click.group(chain=True)
def cli():
    print('cli() has been called')

...

@cli.command()
def all():
    cli(args=['step1', 'step2', 'step3'])

How else could this be done without the side effect of calling cli() twice?

like image 245
Leonid Shvechikov Avatar asked Dec 26 '17 20:12

Leonid Shvechikov


People also ask

What is alias in commands?

An alias lets you create a shortcut name for a command, file name, or any shell text. By using aliases, you save a lot of time when doing tasks you do frequently.

Can alias run multiple commands?

Aliasing multiple commandsYou can combine multiple commands in an alias by separating them with a semicolon, or by using &&, which will run the next command only if the previous command succeeds. This will cd to the top-level git directory, check out the main branch, and run gitpull.

What is an alias command give any example of it?

An alias replaces a string that invokes a command in the Linux shell with another user-defined string. Aliases are mostly used to replace long commands, improving efficiency and avoiding potential spelling errors. Aliases can also replace commands with additional options, making them easier to use.


1 Answers

One way to provide some aliases is to intercept the command and directly manipulate the args list. That can be done with a custom class like:

Custom Class

This class overrides the click.Group.__call__() method to allow editing the args list before calling the command processor. In addition it overrides format_epilog to add help documentation for the aliases.

class ExpandAliasesGroup(click.Group):

    def __init__(self, *args, **kwargs):
        self.aliases = kwargs.pop('aliases', {})
        super(ExpandAliasesGroup, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if args and args[0] and args[0][0] in self.aliases:
            alias = self.aliases[args[0][0]]
            args[0].pop(0)
            for command in reversed(alias):
                args[0].insert(0, command)
        return super(ExpandAliasesGroup, self).__call__(*args, **kwargs)

    @property
    def alias_help(self):
        return '\n'.join(
            '{}:  {}'.format(alias, ' '.join(commands))
            for alias, commands in sorted(self.aliases.items())
        )

    def format_epilog(self, ctx, formatter):
        """Inject our aliases into the help string"""

        if self.aliases:
            formatter.write_paragraph()
            formatter.write_text('Aliases:')
            with formatter.indentation():
                formatter.write_text(self.alias_help)

        # call the original epilog
        super(ExpandAliasesGroup, self).format_epilog(ctx, formatter)

Using the Custom Class

By passing the cls parameter, and a dict of aliases to the click.group() decorator, the ExpandAliasesGroup class can do alias expansion.

aliases = dict(all='command1 command2 command3'.split())

@click.group(chain=True, cls=ExpandAliasesGroup, aliases=aliases)
def cli():
    ....

How does this work?

This works because click is a well designed OO framework. The @click.group() decorator usually instantiates a click.Group object but allows this behavior to be over ridden with the cls parameter. So it is a relatively easy matter to inherit from click.Group in our own class and over ride the desired methods.

By overriding the __call__ method we can intercept all command calls. Then if the list of args starts with a known alias, we edit the args list by removing that aliased command and replacing it with the aliases.

By overriding the format_epilog method we can add help documentation for the aliases.

Test Code:

import click

aliases = dict(all='command1 command2 command3'.split())

@click.group(cls=ExpandAliasesGroup, chain=True, aliases=aliases)
def cli():
    pass

@cli.command()
def command1():
    click.echo('Command 1')

@cli.command()
def command2():
    click.echo('Command 2')

@cli.command()
def command3():
    click.echo('Command 3')

if __name__ == "__main__":
    commands = (
        'command1',
        'command3',
        'command1 command2',
        'all',
        '--help',
    )

    for cmd in commands:
        try:
            print('-----------')
            print('> ' + cmd)
            cli(cmd.split())
        except:
            pass        
    

Test Results:

-----------
> command1
Command 1
-----------
> command3
Command 3
-----------
> command1 command2
Command 1
Command 2
-----------
> all
Command 1
Command 2
Command 3
-----------
> --help
Usage: test.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

Options:
  --help  Show this message and exit.

Commands:
  command1  Command #1 comes first
  command2  Command #2 is after command #1
  command3  Command #3 saves the best for last

Aliases:
  all:  command1 command2 command3
like image 69
Stephen Rauch Avatar answered Oct 21 '22 22:10

Stephen Rauch