Picocliを使用してJavaコマンドラインプログラムを作成する

  • Java

  • link:/category/programming/ [プログラミング]

1. 前書き

このチュートリアルでは、https://picocli.info/ [_picocli_ library]にアプローチします。これにより、Javaでコマンドラインプログラムを簡単に作成できます。
まず、Hello Worldコマンドを作成することから始めます。 次に、__ git __commandを部分的に再現することにより、ライブラリの主要な機能について詳しく説明します。

2. Hello World Command

簡単なことから始めましょう:Hello Worldコマンド!
まず最初に、https://search.maven.org/search?q = g:info.picocli%20AND%20a:picocli [_picocli_プロジェクトへの依存関係]を追加する必要があります。
<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli</artifactId>
    <version>3.9.6</version>
</dependency>
ご覧のとおり、library_3.9.6_バージョンのライブラリを使用しますが、_4.0.0_バージョンは現在作成中です(現在、アルファテストで利用可能です)。
依存関係が設定されたので、Hello Worldコマンドを作成しましょう。 それを行うために、*ライブラリからの_ @ Command_アノテーションを使用します*:
@Command(
  name = "hello",
  description = "Says hello"
)
public class HelloWorldCommand {
}
ご覧のとおり、アノテーションはパラメーターを取ることができます。 ここではそのうちの2つだけを使用しています。 それらの目的は、自動ヘルプメッセージの現在のコマンドとテキストに関する情報を提供することです。
現時点では、このコマンドでできることはあまりありません。 何かをするために、*便利な_CommandLine.run(Runnable、String [])_メソッド*を呼び出す_main_メソッドを追加する必要があります。 これには2つのパラメーターが必要です。つまり、_Runnable_インターフェイスを実装する必要があるコマンドのインスタンスと、コマンド引数(オプション、パラメーター、およびサブコマンド)を表す_String_配列です。
public class HelloWorldCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new HelloWorldCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("Hello World!");
    }
}
_main_メソッドを実行すると、コンソールが_“ Hello World!” _を出力することがわかります。
link:/java-create-jar[jarにパッケージ化されている場合]では、_java_コマンドを使用してHello Worldコマンドを実行できます。
java -cp "pathToPicocliJar;pathToCommandJar" com.baeldung.picoli.helloworld.HelloWorldCommand
驚くことではありませんが、_“ Hello World!” _文字列もコンソールに出力されます。

3. 具体的なユースケース

基本を確認したので、_picocli_ライブラリについて詳しく見ていきます。 そのために、人気のあるコマンド_git_を部分的に再現します。
もちろん、目的は_git_コマンドの振る舞いを実装することではなく、_git_コマンドの可能性を再現することです。どのサブコマンドが存在し、どのサブオプションが固有のサブコマンドで利用できるかです。
まず、Hello Worldコマンドの場合と同様に、_GitCommand_クラスを作成する必要があります。
@Command
public class GitCommand implements Runnable {
    public static void main(String[] args) {
        CommandLine.run(new GitCommand(), args);
    }

    @Override
    public void run() {
        System.out.println("The popular git command");
    }
}

4. サブコマンドの追加

__git __commandは、多くのhttps://picocli.info/#_subcommands[subcommands] — _add、commit、remote_などを提供します。 ここでは、_add_と_commit_に焦点を当てます。
したがって、ここでの目標は、これら2つのサブコマンドをメインコマンドに宣言することです。 _Picocli_は、これを実現する3つの方法を提供します。

4.1. クラスでの_ @ Command_注釈の使用

  • _ @ Command_注釈は、_subcommands_パラメーターを介してサブコマンドを登録する可能性を提供します*:

@Command(
  subcommands = {
      GitAddCommand.class,
      GitCommitCommand.class
  }
)
この例では、_GitAddCommand_と_GitCommitCommand_の2つの新しいクラスを追加します。 両方に_ @ Command_アノテーションが付けられ、Runnable_が実装されます。 *名前は_picocli_が使用するサブコマンドを認識するために使用されるため、名前を付けることが重要です:*
@Command(
  name = "add"
)
public class GitAddCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Adding some files to the staging area");
    }
}
 
@Command(
  name = "commit"
)
public class GitCommitCommand implements Runnable {
    @Override
    public void run() {
        System.out.println("Committing files in the staging area, how wonderful?");
    }
}
したがって、_add_を引数としてメインコマンドを実行すると、コンソールは_「ステージング領域へのファイルの追加」_を出力します。

4.2. メソッドでの_ @ Command_アノテーションの使用

サブコマンドを宣言する別の方法は、* GitCommand_クラスでそれらのコマンドを表す_ @ Command_アノテーション付きメソッドを作成することです*。
@Command(name = "add")
public void addCommand() {
    System.out.println("Adding some files to the staging area");
}

@Command(name = "commit")
public void commitCommand() {
    System.out.println("Committing files in the staging area, how wonderful?");
}
そうすれば、ビジネスロジックをメソッドに直接実装でき、それを処理するための個別のクラスを作成する必要はありません。

4.3. プログラムによるサブコマンドの追加

*最後に、_picocli_は、サブコマンドをプログラムで登録する可能性を提供します。
CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.addSubcommand("add", new GitAddCommand());
commandLine.addSubcommand("commit", new GitCommitCommand());
その後、コマンドを実行する必要がありますが、* _CommandLine.run()_メソッドを使用できなくなりました*。 次に、新しく作成したC__ommandLine__オブジェクトで_parseWithHandler()_メソッドを呼び出す必要があります。
commandLine.parseWithHandler(new RunLast(), args);
最も具体的なサブコマンドを実行するよう_picocli_に指示する_RunLast_クラスの使用に注意する必要があります。 _picocli_には、_RunFirst_と_RunAll_の2つのコマンドハンドラーがあります。 前者は最上位のコマンドを実行し、後者はそれらすべてを実行します。
簡易メソッド_CommandLine.run()_を使用する場合、デフォルトで_RunLast_ハンドラーが使用されます。

5. _ @ Option_アノテーションを使用したオプションの管理

5.1. 引数なしのオプション

コマンドにオプションを追加する方法を見てみましょう。 実際、変更されたすべてのファイルを追加する必要があることを_add_コマンドに伝えたいと思います。 これを実現するために、* GitAddCommand_クラスにhttps://picocli.info/#_options[[email protected]_]アノテーション*を付けたフィールドを追加します。
@Option(names = {"-A", "--all"})
private boolean allFiles;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    } else {
        System.out.println("Adding some files to the staging area");
    }
}
ご覧のとおり、注釈は_names_パラメーターを取り、オプションのさまざまな名前を指定します。 したがって、_add_コマンドを_-A_または_–all_で呼び出すと、_allFiles_フィールドが_true_に設定されます。 そのため、オプションを指定してコマンドを実行すると、コンソールには「すべてのファイルをステージング領域に追加しています」と表示されます。

[[“introwith-argument]]
==== 5.2. 引数付きのオプション

先ほど見たように、引数のないオプションの場合、オプションの有無は常に_boolean_値に評価されます。
ただし、引数を取るオプションを登録することは可能です。 *フィールドを別の型として宣言するだけでこれを実行できます。* _commit_コマンドに_message_オプションを追加しましょう。
@Option(names = {"-m", "--message"})
private String message;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (message != null) {
        System.out.println("The commit message is " + message);
    }
}
当然、_message_オプションを指定すると、コマンドはコンソールにコミットメッセージを表示します。 この記事の後半では、ライブラリで処理されるタイプと、他のタイプを処理する方法について説明します。

5.3. 複数の引数を持つオプション

しかし、実際のhttps://git-scm.com/docs/git-commit[_git commit_]コマンドで行われているように、コマンドに複数のメッセージを取得させたい場合はどうでしょうか。 心配いりません。*フィールドを_array_または_Collection_ *にしましょう。これでほぼ完了です。
@Option(names = {"-m", "--message"})
private String[] messages;

@Override
public void run() {
    System.out.println("Committing files in the staging area, how wonderful?");
    if (messages != null) {
        System.out.println("The commit message is");
        for (String message : messages) {
            System.out.println(message);
        }
    }
}
これで、_message_オプションを複数回使用できます。
commit -m "My commit is great" -m "My commit is beautiful"
ただし、オプションを1回だけ指定し、異なるパラメーターを正規表現区切り文字で区切ることもできます。 したがって、_ @ Option_アノテーションの_split_パラメーターを使用できます。
@Option(names = {"-m", "--message"}, split = ",")
private String[] messages;
これで、_-m「私のコミットは素晴らしい」、「私のコミットは美しい」_を渡して、上記と同じ結果を得ることができます。

5.4. 必須オプション

*時々、必要なオプションがあるかもしれません。 デフォルトは_false_する_required_引数は、私たちはそれを行うことができます:*
@Option(names = {"-m", "--message"}, required = true)
private String[] messages;
現在、_message_オプションを指定せずに_commit_コマンドを呼び出すことはできません。 それを行おうとすると、_picocli_はエラーを出力します:
Missing required option '--message=<messages>'
Usage: git commit -m=<messages> [-m=<messages>]...
  -m, --message=<messages>

6. 位置パラメータの管理

6.1. 位置パラメータをキャプチャする

さて、まだ非常に強力ではないため、_add_コマンドに注目しましょう。 すべてのファイルを追加することしか決定できませんが、特定のファイルを追加する場合はどうでしょうか?
別のオプションを使用してそれを行うこともできますが、ここでは、位置パラメーターを使用することをお勧めします。 実際、*位置パラメータは、特定の位置を占有し、サブコマンドでもオプションでもないコマンド引数をキャプチャするためのものです。*
この例では、これにより次のようなことができます。
add file1 file2
位置パラメータをキャプチャするには、* https://picocli.info/#_positional_parameters [_ @ Parameters_]アノテーションを使用します*:
@Parameters
private List<Path> files;

@Override
public void run() {
    if (allFiles) {
        System.out.println("Adding all files to the staging area");
    }

    if (files != null) {
        files.forEach(path -> System.out.println("Adding " + path + " to the staging area"));
    }
}
さて、以前のコマンドは次のように出力されます:
Adding file1 to the staging area
Adding file2 to the staging area

6.2. 位置パラメータのサブセットをキャプチャする

アノテーションの_index_パラメーターのおかげで、どの位置パラメーターをキャプチャするかについて、よりきめ細かくすることが可能です。 インデックスはゼロから始まります。 したがって、以下を定義する場合:
@Parameters(index="2..*")
これは、3番目から最後まで、オプションまたはサブコマンドと一致しない引数をキャプチャします。
インデックスは、範囲または単一の位置を表す単一の数値のいずれかです。

7. 型変換について一言

このチュートリアルの前半で見たように、_picocli_はそれ自体でいくつかの型変換を処理します。 たとえば、複数の値を_arrays_または_Collections_にマップしますが、_add_コマンドに_Path_クラスを使用する場合など、特定のタイプに引数をマップすることもできます。
*実際のところ、_picocli_にはhttps://picocli.info/#_built_in_types [事前に処理された型の束]が付属しています。 つまり、これらの型を自分で変換することを考えずに直接使用できます。*
ただし、コマンド引数を既に処理されているもの以外の型にマップする必要がある場合があります。 幸いなことに、これは* https://picocli.info/#_custom_type_converters [_ITypeConverter_]インターフェイスと、型をコンバーターに関連付ける_CommandLine#registerConverter_メソッドのおかげで可能です*。
_config_サブコマンドを_git_コマンドに追加したいが、存在しない構成要素をユーザーが変更したくないと想像してみましょう。 そこで、これらの要素を列挙型にマップすることにします。
public enum ConfigElement {
    USERNAME("user.name"),
    EMAIL("user.email");

    private final String value;

    ConfigElement(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }

    public static ConfigElement from(String value) {
        return Arrays.stream(values())
          .filter(element -> element.value.equals(value))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("The argument "
          + value + " doesn't match any ConfigElement"));
    }
}
さらに、新しく作成した_GitConfigCommand_クラスで、2つの位置パラメーターを追加しましょう。
@Parameters(index = "0")
private ConfigElement element;

@Parameters(index = "1")
private String value;

@Override
public void run() {
    System.out.println("Setting " + element.value() + " to " + value);
}
このようにして、ユーザーが存在しない構成要素を変更できないようにします。
最後に、コンバータを登録する必要があります。 美しいのは、Java 8以降を使用している場合、_ITypeConverter_インターフェイスを実装するクラスを作成する必要さえないということです。 *ラムダまたはメソッド参照を_registerConverter()_メソッドに渡すことができます:*
CommandLine commandLine = new CommandLine(new GitCommand());
commandLine.registerConverter(ConfigElement.class, ConfigElement::from);

commandLine.parseWithHandler(new RunLast(), args);
これは、_GitCommand main()_メソッドで発生します。 便利な_CommandLine.run()_メソッドを手放さなければならなかったことに注意してください。
未処理の構成要素で使用すると、コマンドはヘルプメッセージに加えて、パラメーターを_ConfigElement_に変換できないことを示す情報を表示します。
Invalid value for positional parameter at index 0 (<element>):
cannot convert 'user.phone' to ConfigElement
(java.lang.IllegalArgumentException: The argument user.phone doesn't match any ConfigElement)
Usage: git config <element> <value>
      <element>
      <value>

8. Spring Bootとの統合

最後に、これらすべてをスプリング化する方法を見てみましょう!
実際、Spring Boot環境内で作業している可能性があり、コマンドラインプログラムでその恩恵を受けたいと考えています。 そのためには、* * __ SpringBootApplication ___ _CommandLineRunner_インターフェイスを実装する必要があります*:
@SpringBootApplication
public class Application implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... args) {
    }
}
さらに、* Spring _ @ Component_アノテーション*を使用してすべてのコマンドとサブコマンドに注釈を付け、_Application_にすべてを自動配線します。
private GitCommand gitCommand;
private GitAddCommand addCommand;
private GitCommitCommand commitCommand;
private GitConfigCommand configCommand;

public Application(GitCommand gitCommand, GitAddCommand addCommand,
  GitCommitCommand commitCommand, GitConfigCommand configCommand) {
    this.gitCommand = gitCommand;
    this.addCommand = addCommand;
    this.commitCommand = commitCommand;
    this.configCommand = configCommand;
}
すべてのサブコマンドを自動配線する必要があることに注意してください。 残念ながら、これは、現時点では、_picocli_が宣言(注釈付き)で宣言されたときにSpringコンテキストからサブコマンドを取得できないためです。 したがって、我々はプログラム的な方法で、自分自身を配線することを行う必要があるでしょう:
@Override
public void run(String... args) {
    CommandLine commandLine = new CommandLine(gitCommand);
    commandLine.addSubcommand("add", addCommand);
    commandLine.addSubcommand("commit", commitCommand);
    commandLine.addSubcommand("config", configCommand);

    commandLine.parseWithHandler(new CommandLine.RunLast(), args);
}
そして今、私たちのコマンドラインプログラムは、Springコンポーネントを使った魅力のように機能します。 したがって、いくつかのサービスクラスを作成してコマンドで使用し、couldSpringに依存性注入を処理させることができます。

9. 結論

この記事では、__picocli __libraryライブラリの主要な機能をいくつか見てきました。 新しいコマンドを作成し、サブコマンドを追加する方法を学びました。 オプションと位置パラメータを扱う多くの方法を見てきました。 さらに、独自の型コンバーターを実装して、コマンドを厳密に型指定する方法を学習しました。 最後に、Spring Bootをコマンドに組み込む方法を説明しました。
もちろん、それについてもっと多くの発見があります。 ライブラリはhttps://picocli.info/ [完全なドキュメント]を提供します。
この記事の完全なコードについては、https://github.com/eugenp/tutorials/tree/master/libraries-2 [our GitHub]で見つけることができます。