Micronaut で CLI アプリケーション + Picocli

シンプルな CLI アプリを作る機会があり、ちょうど Micronaut を試してみたかったので Micronaut で作ってみました。備忘録。

Standalone Command Line Applications - Micronaut

準備

Micronaut は Scoop でインストール。

> scoop search micronaut
'main' bucket:
    micronaut (1.2.6)

> scoop install micronaut
Installing 'micronaut' (1.2.6) [64bit]
micronaut-1.2.6.zip (12.3 MB) [=================================================================================================] 100%
Checking hash of micronaut-1.2.6.zip ... ok.
Extracting micronaut-1.2.6.zip ... done.
Linking ~\scoop\apps\micronaut\current => ~\scoop\apps\micronaut\1.2.6
Creating shim for 'mn'.
'micronaut' (1.2.6) was installed successfully!

> mn --version
| Micronaut Version: 1.2.6
| JVM Version: 1.8.0_212

Scoop についてはこちら。

Scoop on Windows - kntmr-blog

今回試したサンプルのプロジェクトはこちら。

kntmr/playground/micronaut-cli-examples - GitHub

プロジェクト作成

--featuresコンポーネントを指定すると依存関係に追加してくれる。

> mn create-cli-app com.example.app --features http-client

Picocli

Micronaut では Picocli を使って CLI アプリを作る。この Picocli がとても便利。

Micronaut Picocli Configuration - Micronaut

コマンドライン引数は @Option アノテーションを付与したフィールドにバインドされる。さらに、カンマ区切りの値を配列にしてくれたり、ファイルパスを File オブジェクトにしてくれたり。

@Option(names = {"-a"}, split = ",")
String[] array = {}; // 初期値
@Option(names = {"-f"})
File file;

@Mixinコマンドライン引数を1つのオブジェクトにまとめてくれるものかと思ってたけどそうではない。ドキュメントには共通のオプションとパラメータを再利用するためにあると書いてある。

@Mixin
private Options options;
static class Options {
    @Option(names = {"-v", "--verbose"}, description = "...")
    boolean verbose;
    @Option(names = {"-a"}, split = ",")
    String[] array = {};
    @Option(names = {"-f"})
    File file;
}

IDE 上からコマンドライン引数を渡す場合は Gradle Tasks で --args="-v" で実行する。ただ、2つ目以降の引数を正しく認識してくれない...。要調査

run --args="-v -a=foo,bar" #=> Unknown command-line option '-a'.

CLI アプリ終了時に任意のステータスコードを返したい場合は Callable<Integer> を実装して call() の中でステータスコードを返す。もう少しいい書き方があるかもしれない。

@Command(name = "app")
public class AppCommand implements Callable<Integer> {
    public static void main(String[] args) throws Exception {
        int exitStatus = PicocliRunner.execute(AppCommand.class, args);
        System.exit(exitStatus);
    }
    @Override
    public Integer call() throws Exception {
        return 0;
    }
}

その他

Maven を使う場合、mvnw compile exec:execmvnw package でコケることがある。以下の通り、pom.xml を修正する。

CLI generates a non working pom.xml for cli apps (1.2.0) #2031 - GitHub

あと、先日の JJUG CCC 2019 Fall で Picocli のひとが来てたらしい。(家庭の事情で CCC には参加できず...)

remkop.github.io