简体   繁体   English

使用Python和Click创建shell命令行应用程序

[英]Creating a shell command line application with Python and Click

I'm using click ( http://click.pocoo.org/3/ ) to create a command line application, but I don't know how to create a shell for this application. 我正在使用click( http://click.pocoo.org/3/ )来创建命令行应用程序,但我不知道如何为此应用程序创建shell。
Suppose I'm writing a program called test and I have commands called subtest1 and subtest2 假设我正在编写一个名为test的程序,我有一个名为subtest1subtest2的命令

I was able to make it work from terminal like: 我能够从终端工作,如:

$ test subtest1
$ test subtest2

But what I was thinking about is a shell, so I could do: 但我在考虑的是一个shell,所以我可以这样做:

$ test  
>> subtest1  
>> subtest2

Is this possible with click? 点击可以实现吗?

This is not impossible with click, but there's no built-in support for that either. 这不是不可能的点击,但也没有内置的支持。 The first you would have to do is making your group callback invokable without a subcommand by passing invoke_without_command=True into the group decorator (as described here ). 首先要做的是通过将invoke_without_command=True传递给组装饰器(如此处所述), 没有子命令的情况下调用组回调。 Then your group callback would have to implement a REPL. 那么你的组回调就必须实现一个REPL。 Python has the cmd framework for doing this in the standard library. Python具有用于在标准库中执行此操作的cmd框架。 Making the click subcommands available there involves overriding cmd.Cmd.default , like in the code snippet below. 使click子命令可用涉及覆盖cmd.Cmd.default ,如下面的代码片段所示。 Getting all the details right, like help , should be doable in a few lines. 正确地获取所有细节,例如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()

I was trying to do something similar to the OP, but with additional options / nested sub-sub-commands. 我试图做一些类似于OP的东西,但是有额外的选项/嵌套的子子命令。 The first answer using the builtin cmd module did not work in my case; 使用内置cmd模块的第一个答案在我的情况下不起作用; maybe with some more fiddling.. But I did just run across click-shell . 也许有一些更多的摆弄..但我确实只是运行click-shell Haven't had a chance to test it extensively, but so far, it seems to work exactly as expected. 没有机会对其进行广泛测试,但到目前为止,它似乎完全符合预期。

I know this is super old, but I've been working on fpbhb's solution to support options as well. 我知道这是超级老,但我一直在研究fpbhb的解决方案以支持选项。 I'm sure this could use some more work, but here is a basic example of how it could be done: 我确信这可以使用更多的工作,但这是一个如何完成它的基本示例:

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

There is now a library called click_repl that does most of the work for you. 现在有一个名为click_repl的库可以完成大部分工作。 Thought I'd share my efforts in getting this to work. 我以为我会分享我的努力。

The one difficulty is that you have to make a specific command the repl command, but we can repurpose @fpbhb's approach to allow calling that command by default if another one isn't provided. 一个难点是你必须使用repl命令创建一个特定的命令,但是如果没有提供另一个命令,我们可以重新调整@fpbhb的方法以允许默认调用该命令。

This is a fully working example that supports all click options, with command history, as well as being able to call commands directly without entering the REPL: 这是一个完全有效的示例,它支持所有单击选项,包含命令历史记录,并且无需输入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={})

Here's what it looks like to use the REPL: 以下是使用REPL的样子:

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

And to use the command line subcommands directly: 并直接使用命令行子命令:

$ python pleasntries.py goodnight
Goodnight, moon.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM