如何使用 Click 创建连续/无限的 CLI?

问题描述 投票:0回答:5

我正在尝试使用 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
5个回答
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)

不过,如果有人展示如何通过点击来做到这一点(在我看来,这比火功能更丰富),我会很感激。


1
投票

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


1
投票

这里有一个关于如何使用 Click 执行连续 CLI 应用程序的快速示例:每个函数的 python 单击模块输入

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


0
投票

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

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

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()
    

0
投票
还有另一种方法可以将点击应用程序放入连续循环中。要使此解决方案发挥作用,需要

click>=3

通过稍微调整您的

main()

 函数,使其看起来像这样:

def main(): while True: try: cli.main(standalone_mode=False) except click.exceptions.Abort: break
当前命令完成后,您的应用程序将始终请求另一命令。 

Ctrl-C

Ctrl-D
 可以打破循环。
当您键入退出命令时,您必须举起 
click.exceptions.Abort

您可以在 Click 的文档中阅读更多相关内容:

异常处理调用其他命令

基于您的示例的完整工作代码如下所示:

import click @click.group(invoke_without_command=True) @click.option("--command", prompt=">") @click.pass_context def cli(ctx, command): cmd = cli.get_command(ctx, command) ctx.invoke(cmd) @cli.command(name='c') def command1(): username = click.prompt('Username', type=click.STRING) # other commands... @cli.command(name='q') def quitapp(): raise click.exceptions.Abort() def main(): while True: try: cli.main(standalone_mode=False) except click.exceptions.Abort: break if __name__ == '__main__': main()
希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.