【问题标题】:Creating a shell command line application with Python and Click使用 Python 创建 shell 命令行应用程序并单击
【发布时间】:2015-03-11 15:18:43
【问题描述】:

我正在使用 click (http://click.pocoo.org/3/) 创建命令行应用程序,但我不知道如何为该应用程序创建 shell。
假设我正在编写一个名为 test 的程序,并且我有名为 subtest1subtest2

的命令

我能够从终端使其工作,例如:

$ test subtest1
$ test subtest2

但我想的是一个shell,所以我可以这样做:

$ test  
>> subtest1  
>> subtest2

这可以通过点击实现吗?

【问题讨论】:

标签: python command-line-interface python-click


【解决方案1】:

这对于点击并非不可能,但也没有内置支持。您首先要做的是通过将invoke_without_command=True 传递给组装饰器(如here 所述),使您的组回调可以在没有子命令的情况下调用。然后你的组回调必须实现一个 REPL。 Python 在标准库中有 cmd 框架来执行此操作。使 click 子命令可用涉及覆盖cmd.Cmd.default,如下面的代码 sn-p 所示。只需几行代码就可以搞定所有细节,例如 help

import click
import cmd

class REPL(cmd.Cmd):
    def __init__(self, ctx):
        cmd.Cmd.__init__(self)
        self.ctx = ctx

    def default(self, line):
        subcommand = cli.commands.get(line)
        if subcommand:
            self.ctx.invoke(subcommand)
        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()

@cli.command()
def a():
    """The `a` command prints an 'a'."""
    print "a"

@cli.command()
def b():
    """The `b` command prints a 'b'."""
    print "b"

if __name__ == "__main__":
    cli()

【讨论】:

  • 如何将额外的参数传递给子命令,并让 Click 解析它们?
  • 我知道我玩这个游戏已经很晚了,但请查看我的回答,了解如何处理子命令的附加参数。 (它基于这个答案,没有它我不可能走到这一步)。
【解决方案2】:

现在有一个名为click_repl 的库可以为您完成大部分工作。想我会分享我的努力,让这个工作。

一个困难是您必须将特定命令设为repl 命令,但我们可以重新利用@fpbhb 的方法,以允许在未提供另一个命令时默认调用该命令。

这是一个完整的工作示例,它支持所有点击选项、命令历史记录,以及无需输入 REPL 即可直接调用命令:

import click
import click_repl
import os
from prompt_toolkit.history import FileHistory

@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
    """Pleasantries CLI"""
    if ctx.invoked_subcommand is None:
        ctx.invoke(repl)

@cli.command()
@click.option('--name', default='world')
def hello(name):
    """Say hello"""
    click.echo('Hello, {}!'.format(name))

@cli.command()
@click.option('--name', default='moon')
def goodnight(name):
    """Say goodnight"""
    click.echo('Goodnight, {}.'.format(name))

@cli.command()
def repl():
    """Start an interactive session"""
    prompt_kwargs = {
        'history': FileHistory(os.path.expanduser('~/.repl_history'))
    }
    click_repl.repl(click.get_current_context(), prompt_kwargs=prompt_kwargs)

if __name__ == '__main__':
    cli(obj={})

下面是使用 REPL 的样子:

$ python pleasantries.py
> hello
Hello, world!
> goodnight --name fpbhb
Goodnight, fpbhb.

直接使用命令行子命令:

$ python pleasntries.py goodnight
Goodnight, moon.

【讨论】:

    【解决方案3】:

    我知道这已经很老了,但我一直在研究 fpbhb 的解决方案来支持选项。我确信这可能需要更多的工作,但这里有一个基本示例说明如何完成:

    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()
    
    
    @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)
    
    if __name__ == "__main__":
        cli()
    

    【讨论】:

    • 使用 click 创建交互式 cli 确实很有帮助,但是当我尝试在组级别传递参数时我被卡住了。我尝试像@click.argument('username') 这样在组级别添加一个,但它抛出错误“出现'IndexError'类型的异常并出现消息:'list index out of range'”但无法理解原因。任何人都可以在这方面帮助我。
    【解决方案4】:

    我试图做一些类似于 OP 的事情,但有额外的选项/嵌套的子子命令。使用内置 cmd 模块的第一个答案在我的情况下不起作用;也许还有更多的摆弄......但我确实遇到了click-shell。没有机会对其进行广泛测试,但到目前为止,它似乎完全按预期工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-18
      • 1970-01-01
      • 2016-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多