Skip to content
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

Add options to root command #437

Closed
chishaku opened this issue Oct 10, 2015 · 1 comment
Closed

Add options to root command #437

chishaku opened this issue Oct 10, 2015 · 1 comment

Comments

@chishaku
Copy link

This is potentially related to #430.

Based on the example from the docs, I combine a group of commands under one umbrella with the following custom multicommand class:

class CLI(click.MultiCommand):
    """A CLI derived from commands in a file, list of files, or directory.

    Note:
        Top-level command groups in any files must be set to 'cli' variable.

    Args:
        plugin_files:       Filepath or list of filepaths to plugin files.
        plugin_folder:      A source folder for plugin files.
                A 'commands' folder checked if files or folder args omitted. 
        help:               A description of the CLI.

    Examples:
        >>> cli = CLI()     # Check 'commands' directory for plugin files.

        >>> cli = CLI(plugin_folder='plugins')

        >>> plugins = ['tools/app.py', 'main.py']
        >>> cli = CLI(plugin_files=plugins)

        >>> plugin = 'app.py'
        >>> cli = CLI(plugin_files=plugin, help='My amazing cli.')

    """

    def __init__(self, 
            plugin_files=None, 
            plugin_folder='commands',
            help="A custom command life interface.",
            **kw):
        """Generate the CLI from specified plugin files."""

        super(CLI, self).__init__(**kw)
        self.plugin_files = plugin_files
        self.plugin_folder = plugin_folder
        self.help = help

        # A dictionary of commands/files.  Key=command/file, Value=directory.
        self.plugins = {}

        # A function to parse available commands and their source paths as 
        # derived from the plugin files or folder specified.
        self.source_plugins()

    def source_plugins(self):
        """Source plugin files from the specified file(s) or folder."""

        plugins = []
        if isinstance(self.plugin_files, list):
            plugins.extend(self.plugin_files)
        elif isinstance(self.plugin_files, str):
            plugins.append(self.plugin_files)
        else:
            for fn in os.listdir(self.plugin_folder):
                plugins.append(os.path.join(self.plugin_folder, fn))
        for filepath in plugins:
            directory, filename = os.path.split(filepath)
            command = filename.replace('.py', '')
            self.plugins[command] = directory

    def list_commands(self, ctx):
        rv = []
        for filepath in self.plugins:
            directory, fn = os.path.split(filepath)
            rv.append(fn)
        # rv.sort()
        return rv

    def get_command(self, ctx, name):
        ns = {}
        try:
            fn = os.path.join(self.plugins[name], name + '.py')
        except (KeyError, ), e:
            secho('red', '"{}" is an invalid command.'.format(name))
            sys.exit(1)
        try:
            with open(fn) as f:
                code = compile(f.read(), fn, 'exec')
                eval(code, ns, ns)
        except (IOError, ), e:
            secho('red', ('The specified "{}" plugin ' 
                    'file does not exist.').format(fn))
            sys.exit(1)
        try:
            return ns['cli']
        except (KeyError,), e:
            secho('red', ('"cli" command group was not '
                    'found in the "{}" plugin.').format(fn))
            sys.exit(1)

I then use the class like this:

# app.py

if __name__ == '__main__':
    plugins = ['plugin.py', 'another_plugin.py']
    cli = CLI(plugin_files=plugins, help=__doc__)
    cli()

$ python app.py plugin arg

Is there anyway to add options after the call to app.py but before the call to the plugin?

@chishaku
Copy link
Author

Figured this out. I just instantiated a click.Option and passed it to the params attribute of the custom MultiCommand class.

# app.py

if __name__ == '__main__':
    plugins = ['plugin.py', 'another_plugin.py']

    env_option = click.Option(param_decls=['-e', '--env'], default='prod', help='Environment config')
    params = [env_option,]

    cli = CLI(plugin_files=plugins, params=params, help=__doc__)
    cli()

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant