astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

pythonのArgumentParserような使い心地!picocliのご紹介

f:id:astamuse:20200415113243p:plain

こんにちは、開発部の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は機能が豊富でドキュメント量が多いのでもっと色んなことができそうです。

まだまだ盛んに開発が行われているので、コマンドやバッチの実装などがある方は導入を検討してみるのもいいと思います。

それでは。

Copyright © astamuse company, ltd. all rights reserved.