【问题标题】:How can I create a continuous / infinite CLI with Click?如何使用 Click 创建连续/无限 CLI?
【发布时间】:2019-10-21 11:38:55
【问题描述】:

我正在尝试使用 Click 为我的 Python 3 应用程序创建 CLI。基本上我需要应用程序连续运行,等待用户命令并执行它们,如果输入特定命令(比如“q”)则退出。在 Click 文档或其他地方找不到示例。

交互式 shell 的示例如下:

myapp.py
> PLEASE ENTER LOGIN: 
mylogin
> PLEASE ENTER PASSWORD:
mypwd
> ENTER COMMAND: 
a
> Wrong command! 
> USAGE: COMMAND [q|s|t|w|f] OPTIONS ARGUMENTS
> ENTER COMMAND:
f
> (output of "f" command...)
> ENTER COMMAND:
q
> QUITTING APP...

我试过这样:

import click

quitapp = False # global flag

@click.group()
def cli():
    pass

@cli.command(name='c')
@click.argument('username')
def command1(uname):
    pass # do smth

# other commands...

@cli.command(name='q')
def quitapp():
    global quitapp
    quitapp = True

def main():    
    while not quitapp:
        cli()

if __name__ == '__main__':
    main()

但控制台只运行一次应用程序。

【问题讨论】:

  • 是的,我愿意。正如我在下面评论的那样,我用火做了一个自定义的,但它缺少许多功能,如自动完成等。

标签: python-3.x command-line-interface python-click


【解决方案1】:

我实际上已经切换到 fire 并设法制作了一个类似 shell 的连续函数,如下所示:

COMMAND_PROMPT = '\nCOMMAND? [w to quit] >'
CAPTCHA_PROMPT = '\tEnter captcha text (see your browser) >'
BYE_MSG = 'QUITTING APP...'
WRONG_CMD_MSG = 'Wrong command! Type "h" for help.'
EMPTY_CMD_MSG = 'Empty command!'

class MyClass:
    def __init__(self):
        # dict associating one-letter commands to methods of this class
        self.commands = {'r': self.reset, 'q': self.query, 'l': self.limits_next, 'L': self.limits_all, 
                'y': self.yandex_logo, 'v': self.view_params, 'h': self.showhelp, 'c': self.sample_captcha, 'w': None}
        # help (usage) strings
        self.usage = '\nUSAGE:\t[{}] [value1] [value2] [--param3=value3] [--param4=value4]'.format('|'.join(sorted(self.commands.keys())))
        self.usage2 = '\t' + '\n\t'.join(['{}:{}'.format(fn, self.commands[fn].__doc__) for fn in self.commands if fn != 'w'])

    def run(self):
        """
        Provides a continuously running commandline shell.        
        The one-letter commands used are listed in the commands dict.
        """
        entered = ''
        while True:
            try:
                print(COMMAND_PROMPT, end='\t')
                entered = str(input())
                if not entered:
                    print(EMPTY_CMD_MSG)
                    continue
                e = entered[0]
                if e in self.commands:
                    if self.commands[e] is None: 
                        print(BYE_MSG)
                        break
                    cmds = entered.split(' ')
                    # invoke Fire to process command & args
                    fire.Fire(self.commands[e], ' '.join(cmds[1:]) if len(cmds) > 1 else '-')
                else:
                    print(WRONG_CMD_MSG)
                    self.showhelp()
                    continue     
            except KeyboardInterrupt:
                print(BYE_MSG)
                break

            except Exception:
                continue

    # OTHER METHODS...

if __name__ == '__main__':
    fire.Fire(MyClass)

不过,如果有人展示了如何使用 click 来实现这一点,我将不胜感激(在我看来,这似乎比 fire 功能更丰富)。

【讨论】:

    【解决方案2】:

    我终于找到了 Python 中用于交互式 shell 的其他库:cmd2prompt,它们对于开箱即用的类似 REPL 的 shell 来说要先进得多...

    【讨论】:

      【解决方案3】:

      这里有一个快速示例,说明如何使用单击此处执行连续 CLI 应用程序:python click module input for each function

      它只有一种在循环上运行单击命令的方式,但您可以在命令或循环主体中放入任何您想要的自定义逻辑。希望对您有所帮助!

      【讨论】:

        【解决方案4】:

        Here 我发现 click 在循环中,但是当我们尝试使用具有不同选项的不同命令时,它容易出错

        !警告:这不是一个完美的解决方案

        import click
        import cmd
        import sys
        
        from click import BaseCommand, UsageError
        
        
        class REPL(cmd.Cmd):
            def __init__(self, ctx):
                cmd.Cmd.__init__(self)
                self.ctx = ctx
        
            def default(self, line):
                subcommand = line.split()[0]
                args = line.split()[1:]
        
                subcommand = cli.commands.get(subcommand)
                if subcommand:
                    try:
                        subcommand.parse_args(self.ctx, args)
                        self.ctx.forward(subcommand)
                    except UsageError as e:
                        print(e.format_message())
                else:
                    return cmd.Cmd.default(self, line)
        
        
        @click.group(invoke_without_command=True)
        @click.pass_context
        def cli(ctx):
            if ctx.invoked_subcommand is None:
                repl = REPL(ctx)
                repl.cmdloop()
        
        # Both commands has --foo but if it had different options,
        # it throws error after using other command
        @cli.command()
        @click.option('--foo', required=True)
        def a(foo):
            print("a")
            print(foo)
            return 'banana'
        
        
        @cli.command()
        @click.option('--foo', required=True)
        def b(foo):
            print("b")
            print(foo)
        
        # Throws c() got an unexpected keyword argument 'foo' after executing above commands
        @cli.command()
        @click.option('--bar', required=True)
        def c(bar):
            print("b")
            print(bar)
        
        if __name__ == "__main__":
            cli()
        

        【讨论】:

          猜你喜欢
          • 2018-12-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-12-11
          • 2021-02-24
          相关资源
          最近更新 更多