Parsing Command-Line Parameters with Airline – 用Airline解析命令行参数

最后修改: 2020年 1月 27日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll introduce Airline — an annotation-driven Java library for building Command-Line Interfaces (CLIs).

在本教程中,我们将介绍Airline–一个用于构建命令行界面(CLI)的注释驱动的Java库。

2. Scenario

2.场景

When building a command-line application, it’s natural to create a simple interface to allow the user to mold the output as needed. Almost everyone has played with Git CLI and can relate to how powerful, yet simple, it is. Alas, few tools come in handy when building such an interface.

在构建一个命令行应用程序时,自然要创建一个简单的界面,让用户根据需要来塑造输出。几乎每个人都玩过Git CLI,都能体会到它的强大和简单。然而,在建立这样一个界面时,很少有工具能派上用场。

The airline aims to reduce the boilerplate code typically associated with CLIs in Java, as most common behaviors can be achieved with annotations and zero user code.

航空公司 旨在减少通常与Java中CLI相关的模板代码,因为大多数常见的行为可以通过注释和零用户代码实现。

We’re going to implement a small Java program that will exploit Airline’s functionalities to mimic a common CLI. It’ll expose user commands for setting up our program configuration, like defining the database URL, credentials, and logger verbosity. We’ll also dive under the surface of our library and use more than its basics to probe if it can handle some complexity.

我们将实现一个小的Java程序,它将利用Airline的功能来模仿一个普通的CLI。它将公开用户命令,用于设置我们的程序配置,如定义数据库URL、证书和记录器的粗略程度。我们还将深入到我们的库的表面之下,使用比它的基本功能更多的东西来探测它是否能处理一些复杂性。

3. Setup

3.设置

To get started, let’s add the Airline dependency to our pom.xml:

为了开始,让我们把Airline依赖性添加到我们的pom.xml。

<dependency>
    <groupId>com.github.rvesse</groupId>
    <artifactId>airline</artifactId>
    <version>2.7.2</version>
</dependency>

4. A Simple CLI

4.一个简单的CLI

Let’s create our entry point for the application — the CommandLine class:

让我们创建应用程序的入口点–CommandLine类。

@Cli(name = "baeldung-cli",
  description = "Baeldung Airline Tutorial",
  defaultCommand = Help.class)
public class CommandLine {
    public static void main(String[] args) {
        Cli<Runnable> cli = new Cli<>(CommandLine.class);
        Runnable cmd = cli.parse(args);
        cmd.run();
    }
}

Through a simple @Cli annotation, we have defined the default command that will run on our application – the Help command.

通过一个简单的@Cli注解,我们已经定义了将在我们的应用程序上运行的默认命令–Help命令。

The Help class comes as part of the Airline library and exposes a default help command using -h or –help options.

Help类作为Airline库的一部分,使用-h-help选项暴露了一个默认的帮助命令。

Just like that, the basic setup is done.

就这样,基本设置已经完成。

5. Our First Command

5.我们的第一个命令

Let’s implement our first command, a simple LoggingCommand class that will control the verbosity of our logs. We’ll annotate the class with @Command to ensure that the correct command is applied when the user calls setup-log:

让我们来实现我们的第一个命令,一个简单的LoggingCommand类,它将控制我们日志的粗略程度。我们将用@Command来注解这个类,以确保在用户调用setup-log时应用正确的命令。

@Command(name = "setup-log", description = "Setup our log")
public class LoggingCommand implements Runnable {

    @Inject
    private HelpOption<LoggingCommand> help;
	
    @Option(name = { "-v", "--verbose" }, 
      description = "Set log verbosity on/off")
    private boolean verbose = false;

    @Override
    public void run() {
        if (!help.showHelpIfRequested())
            System.out.println("Verbosity: " + verbose);
        }
    }
}

Let’s take a closer look at our example command.

让我们仔细看看我们的示例命令。

First, we’ve set a description so that our helper, thanks to the injection, will display our command options when requested.

首先,我们设置了一个描述,这样我们的帮助器,由于注入的原因,会在请求时显示我们的命令选项。

Then we declared a boolean variable, verbose, and annotated it with @Option to give it a name, description, and also an alias -v/–verbose to represent our command-line option to control verbosity.

然后我们声明了一个boolean变量,verbose,并用@Option来注解它,给它一个名字、描述,还有一个别名-v/-verbose,代表我们的命令行选项来控制粗暴程度。

Finally, inside the run method, we instructed our command to halt whenever the user asks for help.

最后,在run方法里面,我们指示我们的命令在用户要求帮助的时候停止运行。

So far, so good. Now, we need to add our new command to the main interface by modifying the @Cli annotation:

到目前为止,很好。现在,我们需要通过修改@Cli注解将我们的新命令添加到主界面。

@Cli(name = "baeldung-cli",
description = "Baeldung Airline Tutorial",
defaultCommand = Help.class,
commands = { LoggingCommand.class, Help.class })
public class CommandLine {
    public static void main(String[] args) {
        Cli<Runnable> cli = new Cli<>(CommandLine.class);
        Runnable cmd = cli.parse(args);
        cmd.run();
    }
}

Now, if we pass setup-log -v to our program, it will run our logic.

现在,如果我们把setup-log -v传给我们的程序,它将运行我们的逻辑。

6. Constraints and More

6.制约因素和更多

We have seen how Airline generates CLI flawlessly, but… there’s more!

我们已经看到Airline是如何完美地生成CLI的,但是……还有更多的内容

We can specify constraints (or restrictions) for our parameters to handle allowed values, requirements or dependencies, and more.

我们可以为我们的参数指定约束(或限制),以处理允许的值、要求或依赖性,以及更多。

We’re going to create a DatabaseSetupCommand class, which will respond to the setup-db command; same as we did earlier, but we’ll add some spice.

我们将创建一个DatabaseSetupCommand类,它将响应setup-db命令;和我们之前做的一样,但我们将添加一些调料。

First, we’ll request the type of database, accepting only 3 valid values through @AllowedRawValues:

首先,我们将请求数据库的类型,通过@AllowedRawValues只接受3个有效值。

@AllowedRawValues(allowedValues = { "mysql", "postgresql", "mongodb" })
@Option(type = OptionType.COMMAND,
  name = {"-d", "--database"},
  description = "Type of RDBMS.",
  title = "RDBMS type: mysql|postgresql|mongodb")
protected String rdbmsMode;

When using a database connection, without any doubt, users should supply an endpoint and some credentials to access it. We’ll let CLI handle this through one (URL mode) or more parameters (host mode). For this, we’ll use the @MutuallyExclusiveWith annotation, marking each parameter with the same tag:

当使用数据库连接时,毫无疑问,用户应该提供一个端点和一些凭证来访问它。我们将让CLI通过一个(URL模式)或多个参数(host模式)来处理。为此,我们将使用@MutuallyExclusiveWith注解,用相同的标签标记每个参数。

@Option(type = OptionType.COMMAND,
  name = {"--rdbms:url", "--url"},
  description = "URL to use for connection to RDBMS.",
  title = "RDBMS URL")
@MutuallyExclusiveWith(tag="mode")
@Pattern(pattern="^(http://.*):(d*)(.*)u=(.*)&p=(.*)")
protected String rdbmsUrl = "";
	
@Option(type = OptionType.COMMAND,
  name = {"--rdbms:host", "--host"},
  description = "Host to use for connection to RDBMS.",
  title = "RDBMS host")
@MutuallyExclusiveWith(tag="mode")
protected String rdbmsHost = "";

Note that we used the @Pattern decorator, which helps us define the URL string format.

注意,我们使用了@Pattern装饰器,它帮助我们定义URL字符串格式。

If we look at the project documentation, we’ll find other valuable tools for handling requirements, occurrences, allowed values, specific cases, and more, enabling us to define our custom rules.

如果我们看一下项目文档,我们会发现其他有价值的工具,用于处理需求、发生率、允许值、特定情况等等,使我们能够定义我们的自定义规则

Finally, if the user selected the host mode, we should ask them to provide their credentials. In this way, one option is dependent on another. We can achieve this behavior with the @RequiredOnlyIf annotation:

最后,如果用户选择了主机模式,我们应该要求他们提供他们的凭证。这样一来,一个选项依赖于另一个选项。我们可以用@RequiredOnlyIf注解来实现这种行为。

@RequiredOnlyIf(names={"--rdbms:host", "--host"})
@Option(type = OptionType.COMMAND,
  name = {"--rdbms:user", "-u", "--user"},
  description = "User for login to RDBMS.",
  title = "RDBMS user")
protected String rdbmsUser;

@RequiredOnlyIf(names={"--rdbms:host", "--host"})
@Option(type = OptionType.COMMAND,
  name = {"--rdbms:password", "--password"},
  description = "Password for login to RDBMS.",
  title = "RDBMS password")
protected String rdbmsPassword;

What if we need to use some drivers to handle the DB connection? And also, suppose we need to receive more than one value in a single parameter. We can just change the option type to OptionType.ARGUMENTS or – even better – accept a list of values:

如果我们需要使用一些驱动来处理DB的连接呢?还有,假设我们需要在一个参数中接收一个以上的值。我们只需将选项类型改为OptionType.ARGUMENTS或者–甚至更好–接受一个值的列表。

@Option(type = OptionType.COMMAND,
  name = {"--driver", "--jars"},
  description = "List of drivers",
  title = "--driver <PATH_TO_YOUR_JAR> --driver <PATH_TO_YOUR_JAR>")
protected List<String> jars = new ArrayList<>();

Now, let’s not forget to add the database setup command to our main class. Otherwise, it won’t be available on CLI.

现在,我们不要忘记在我们的主类中添加数据库设置命令。否则,它将无法在CLI上使用。

7. Run

7.运行

We did it! We finished our project, and now we can run it.

我们成功了!我们完成了我们的项目,现在我们可以运行它了。

As expected, without passing any parameters, Help is invoked:

正如预期的那样,没有传递任何参数,Help被调用。

$ baeldung-cli

usage: baeldung-cli <command> [ <args> ]

Commands are:
    help        Display help information
    setup-db    Setup our database
    setup-log   Setup our log

See 'baeldung-cli help <command>' for more information on a specific command.

If we instead execute setup-log –help, we get:

如果我们转而执行setup-log -help,我们会得到。

$ baeldung-cli setup-log --help

NAME
        baeldung-cli setup-log - Setup our log

SYNOPSIS
        baeldung-cli setup-log [ {-h | --help} ] [ {-v | --verbose} ]

OPTIONS
        -h, --help
            Display help information

        -v, --verbose
            Set log verbosity on/off

Finally, supplying parameters to these commands will run the correct business logic.

最后,向这些命令提供参数将运行正确的业务逻辑。

8. Conclusion

8.结语

In this article, we have built a simple yet powerful command-line interface with very little coding.

在这篇文章中,我们用很少的编码建立了一个简单而强大的命令行界面。

The Airline library, with its powerful functionalities, simplifies the CLI, providing us a general, clean and reusable infrastructure. It allows us, developers, to concentrate on our business logic rather than spending time designing what should be trivial.

Airline库以其强大的功能简化了CLI,为我们提供了一个通用的、干净的、可重用的基础设施。它使我们这些开发人员能够专注于我们的业务逻辑,而不是花时间去设计那些应该是微不足道的东西。

As always, the code can be found over on GitHub.

一如既往,代码可以在GitHub上找到over