こんにちは、開発部のnishikawaです。本日はpicocliというライブラリを使ってScalaでコマンドを実装する機会があったのでご紹介します。
コマンド作りあるある
コマンドを作る時によく実装されるのが引数のパースです。これはどんな言語で実装されていても多くの人が実装すると思います。よく使われるのがgetoptsですが、これを使っても毎回引数のパースを作り込むのは面倒です。
もちろんjavaも例外ではないのですが、この実装が面倒で時間がない時はバリデーションを省略するような実装をすることも多いと思います。
コマンド作るならやっぱりpythonだよねー
その点pythonだと、デフォルトで高機能な引数のパーサーがあります。それがArgumentParserです。これが存在することや便利なライブラリがあるのでコマンド作りはpythonでという人は多いと思います。
しかし、プロダクトの多くをJVM言語で実装しているようなチームでは資産が流用できないのでやっぱりpythonは・・・というようなところも多いかもしれません。じゃあJVM言語でもArgumentParserのような機能が欲しいということで出てくるのがpicocliです。
picocli とは
picocliはjavaで実装されたコマンドラインインターフェースを提供するライブラリです。とても高機能でJVM言語で利用可能です。
picocli: https://github.com/remkop/picocli
pythonのArgumentParserと比較しながら使ってみる
picocliはArgumentParserのような使い心地です。実際にpythonのArgumentParserと比較してみたいと思います。
以下は引数に数字を列挙すると最大値を返し、オプションで「--sum」をつけると列挙した引数の数字を合計するコマンドをArgumentParserとpicocliを利用して実装した例です。
ArgumentParser
まずはpythonのArgumentParserを使った例です。
test.py
import sys from argparse import ArgumentParser, _SubParsersAction import argparse def main(): parser = argparse.ArgumentParser(description='Process some integers.') parser.add_argument('integers', metavar='N', type=int, nargs='+', help='an integer for the accumulator') parser.add_argument('--sum', dest='accumulate', action='store_const', const=sum, default=max, help='sum the integers (default: find the max)') args = parser.parse_args() print(args.accumulate(args.integers)) return 0 if __name__ == '__main__': sys.exit(main())
サンプルを実装したら、実行してみます。
ヘルプ実行例
$ python3 test.py -h usage: test.py [-h] [--sum] N [N ...] Process some integers. positional arguments: N an integer for the accumulator optional arguments: -h, --help show this help message and exit --sum sum the integers (default: find the max) $
通常実行(--sumオプションなし)
$ python3 test.py 1 2 3 3 $
通常実行(--sumオプションあり)
$ python3 test.py 1 2 3 --sum 6 $
picocli
TestCommand.scala
package com.example import java.util.concurrent.Callable import picocli.CommandLine import picocli.CommandLine.{Command, Option, Parameters} import scala.util.{Failure, Success, Try}; object TestCommand { def main(args: Array[String]) = { System.exit(new CommandLine(new TestCommand).execute(args: _*)) } } @Command(name = "test", mixinStandardHelpOptions = true, version = Array("test sample command 1.0.0")) class TestCommand extends Callable[Int] { @Parameters(arity = "1..*", paramLabel = "N", description = Array("an integer for the accumulator")) var integers: Array[Int] = Array() @Option(names = Array("-s", "--sum"), description = Array("sum the integers (default: find the max)")) var isSum: Boolean = false override def call(): Int = { Try { if (isSum) integers.sum else integers.max } match { case Success(result) => println(result) 0 case Failure(exception) => exception.printStackTrace() 1 } } }
サンプルを実装したら、実行してみます。(scalaの例ではassemblyで実行可能なjarにあらかじめパッケージングしております)
ヘルプ実行例
$ java -jar test.jar -h Usage: test [-hsV] N... N... an integer for the accumulator -h, --help Show this help message and exit. -s, --sum sum the integers (default: find the max) -V, --version Print version information and exit. $
通常実行(--sumオプションなし)
$ java -jar test.jar 1 2 3 3 $
通常実行(--sumオプションあり)
$ java -jar test.jar 1 2 3 --sum 6 $
まとめ
- コマンド実装をJVM言語で行う時にはpicocliを利用すると面倒な引数のパースを簡略化することができる
- 使い勝手はpythonのArgumentParserに近くとても使いやすい
ArgumentParserとpicocliを使ってみての感想
以上、pythonとscalaで二つのライブラリを使用して同じコマンドを実装してみましたが、言語差異によるコンパイルなどの手間はありますが、それ以外はpythonでの実装に近い使い勝手だったかなと思いました。
また、今回は簡単な例を実装しましたが、picocliは機能が豊富でドキュメント量が多いのでもっと色んなことができそうです。
まだまだ盛んに開発が行われているので、コマンドやバッチの実装などがある方は導入を検討してみるのもいいと思います。
それでは。