見出し画像

picocliでJavaの引数処理を手軽に行う話

あいさつに代えて

今回の画像は、先日自宅近くで撮影した朝の空です。まさに雲一つない空ですね。まるで壁紙の様です。

picocliとは

picocliとは、Javaの引数処理を少ない手間で行うことができるライブラリです。実は前回の記事でも利用していました。
主な機能・特徴としては、次の通りです。

  • 1ファイルで他のライブラリに依存しない

  • いろんな構文スタイルに対応

  • サブコマンドに対応

  • その他、豊富な機能

    • 引数をファイルで指定する@-filesに対応

    • ヘルプの色表示に対応

    • BashやZSHのTAB補完を行う為のスクリプト生成に対応

    • GraalVM Native Image用の設定の生成に対応(picocli-codegen)

    • 文字列リソースのI18Nに対応

機能が豊富で全部に触れるのは難しいので、今回はある程度の内容を簡単に使ってみたいと思います。

使ってみる

依存関係

記載時の最新バージョンは4.7.1です。

Gradleでは、次の様に記載します。

dependencies {
    implementation 'info.picocli:picocli:4.7.1'
}

Mavenでは、次の様に記載します。

<dependency>
  <groupId>info.picocli</groupId>
  <artifactId>picocli</artifactId>
  <version>4.7.1</version>
</dependency>

コマンド例の概要

例として、こんなコマンドを作ります。

  • コマンド表示名:file-tool
    指定したフォルダ内のファイルに関する情報を出力するツール
    複数のサブコマンドを持つ

  • 共通オプション

    • -d, --delimiter:出力時の区切り文字(デフォルトはタブ)

    • --header:先頭行にヘッダ文字列を出力するかどうか(デフォルトは出力しない)

    • -h, --help:コマンド全体のヘルプ表示

    • -V, --version:バージョン表示

  • サブコマンド
    サブコマンドの指定が無い場合はバージョン表示

    • size:指定したフォルダ内の全ファイルについてバイト数を表示

      • フォルダはパラメータで指定、複数指定を許可

      • パラメータが無い場合は現在のフォルダ(システムプロパティuser.dir)

    • type:指定したフォルダ内の全ファイルについてContent-Typeを表示
      Content-Typeの取得はFiles.probeContentType(Path)を使用

      • フォルダはパラメータで指定、複数指定を許可

      • パラメータが無い場合は現在のフォルダ(システムプロパティuser.dir)

    • help:指定したサブコマンドのヘルプ表示
      picocliが提供するpicocli.CommandLine.HelpCommandクラスを利用

      • サブコマンドの指定が無い場合はコマンド全体のヘルプ表示

コマンドの定義と実行

コマンドを定義する為には、RunnableもしくはCallableインターフェースを実装したクラスを作成します。今回はRunnableインターフェースを使います。
このクラスのインスタンスを、picocli.CommandLineクラスのコンストラクタに指定して、execute()メソッドを呼び出します。

public class FileTool implements Runnable {
...
    public static void main(String... args) {
        int exitCode = new CommandLine(new FileTool()).execute(args);
        System.exit(exitCode);
    }
...
}

コマンド名・説明など

コマンドの説明などは、クラス定義に@picocli.CommandLine.Commandアノテーションで指定します。

@Command(name = "file-tool", version = "file-tool 0.1", mixinStandardHelpOptions = true,
        description = "File tool", subcommands = HelpCommand.class, scope = ScopeType.INHERIT)
public class FileTool implements Runnable {
...
  • name:ヘルプで表示するコマンド名

  • description:ヘルプで表示するコマンドの説明文(複数行指定可能)

  • version:バージョンで表示するバージョン情報(複数行指定可能)

  • mixinStandardHelpOptions:自動で標準のヘルプオプションを追加するかどうか(デフォルトfalse)
    次のオプションが自動で追加

    • -h,--help:ヘルプ表示

    • -V,--version:バージョン表示

  • subcommands:サブコマンドで使用するクラス(複数指定可能)
    サブコマンドの指定は後で説明

  • scope:オプション等の表示範囲
    picocli.CommandLine.ScopeType.INHERITを指定するとサブコマンドでも表示

コマンドのオプション

コマンドのオプションは、変数やセッターメソッドに@picocli.CommandLine.Optionアノテーションで指定します。

...
    @Option(names = {"--header"}, negatable = true,
            description = "Show header.",
            scope = ScopeType.INHERIT)
    boolean header;

    @Option(names = {"-d", "--delimiter"}, defaultValue = "\t",
            description = "Set delimiter (default: \\t).", paramLabel = "DELIMITER",
            scope = ScopeType.INHERIT)
    private String delimiter;
...
  • names:オプション名(複数指定可能)
    オプション名の先頭を"-"1文字から始める("-d")と、POSIXオプションの様に複数のオプションをまとめて(例えば"-abcd=,"の様に)指定可能

  • description:オプションの説明文(複数指定可能)

  • paramLabel:オプションの値で表示するラベル

  • defaultValue:オプションの初期値

  • negatable:値が真偽値の場合、反対の値を指定するオプションを自動的に追加
    "--header"オプションに対する"--no-header"オプションが追加

  • scope:このオプションの表示範囲
    picocli.CommandLine.ScopeType.INHERITを指定するとこのオプションはサブコマンドでも表示

サブコマンド

サブコマンドの指定方法には2種類あります。

  1. @picocli.CommandLine.Commandアノテーションを付けたコマンド定義と同様のクラスを作成し、親コマンドのsubcommandsに指定
    helpサブコマンドの指定はこちらを使用(picocli.CommandLine.HelpCommandクラスを利用する為)

  2. @picocli.CommandLine.Commandアノテーションを付けたメソッドを作成
    size,typeサブコマンドはこちらを使用

@Command(name = "file-tool", version = "file-tool 0.1", mixinStandardHelpOptions = true,
        description = "File tool", subcommands = HelpCommand.class, scope = ScopeType.INHERIT)
public class FileTool implements Runnable {
...
    @Command(name = "size", description = "Print file size.")
    void size(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...
    @Command(name = "type", description = "Print file content type.")
    void contentType(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...
  • name:サブコマンド名

パラメータ

パラメータの指定方法は2種類あります。

  1. 変数やセッターメソッドに@picocli.CommandLine.Parametersアノテーションで指定

  2. サブコマンドのメソッド仮引数に@picocli.CommandLine.Parametersアノテーションで指定

今回のサンプルプログラムでは、サブコマンドのメソッド仮引数に@picocli.CommandLine.Parametersアノテーションで指定します。

...
    @Command(name = "size", description = "Print file size.")
    void size(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...
    @Command(name = "type", description = "Print file content type.")
    void contentType(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...
  • description:パラメータの説明文(複数指定可能)

  • paramLabel:パラメータの値で表示するラベル

  • defaultValue:パラメータの初期値

  • index:パラメータの位置指定
    未指定の場合、引数の型がリストや配列なので全てのパラメータを設定

コマンドモデル

バージョン表示を明示的に行う為には、picocli.CommandLineクラスのprintVersionHelp(PrintStream)メソッドが利用できます。
@Specアノテーションでpicocli.CommandLine.Model.CommandSpecクラスのインスタンスをバインドし、commandLine()メソッドでpicocli.CommandLineクラスのインスタンスを取得します。

...
    @Spec
    CommandSpec commandSpec;
...
    public void run() {
        commandSpec.commandLine().printVersionHelp(System.out);
    }
...

変数

picocliでは、アノテーションの属性値に定義された変数を指定する事ができます。

...
    void size(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...
    void contentType(@Parameters(paramLabel = "PATH",
            description = {"File path(s).",
                    "The default is the current directory (${DEFAULT-VALUE})."},
            defaultValue = "${sys:user.dir}") List<Path> paths) {
...

指定できる変数の種類はこちらにありますが、サンプルプログラムで使用しているものを挙げます。

  • ${DEFAULT-VALUE}:defaultValueの値

  • ${sys:key}:システムプロパティkeyの値

サンプルプログラム

今回説明したサンプルプログラムの全体は、次になります。

おまけ

picocliの作者Remko Popmaさんは、Apache Log4J2Apache GroovyのPMC Memberです。
日本在住なので、Java関連のイベントで会えるかもしれません。

この記事が気に入ったらサポートをしてみませんか?