Ruby の OptionParser ライクな Commons-CLI のラッパを書いてみた

定番コマンドライン引数解析ライブラリ Commons-CLI(ver1.2)を使ってみた

import org.apache.commons.cli._

class NoSuchConfigFileException extends RuntimeException

var testMode = false
var configFilename:String = null
var exAttachFiles = Array[String]()
var subjectStr:String = null

new BasicParser {
  val options = new Options {
    addOption("t", false, "test mode")
    addOption("c", true, "config file")
    addOption("a", true, "extra attach file")
    addOption("s", true, "subject")
  }

  try {
    val commandLine = parse(options, args)

    if (commandLine.hasOption("t")) testMode = true
    if (commandLine.getOptionValue("c") != null) configFilename = commandLine.getOptionValue("c")
    if (commandLine.getOptionValue("a") != null) exAttachFiles = commandLine.getOptionValue("a").split(",")
    if (commandLine.getOptionValue("s") != null) subjectStr = commandLine.getOptionValue("s")

    if (configFilename == null) throw new NoSuchConfigFileException
  } catch {
    case e:NoSuchConfigFileException =>
      println("-c は必ず指定してください")
    case e:MissingArgumentException =>
      println(e.getOption + "には引数が必要です。")
    case e:UnrecognizedOptionException =>
      println("不明なオプションです:" + e.getOption)
    case e =>
      throw e
  }
}

println("-t:" + testMode)
println("-c:" + configFilename)
println("-a:" + exAttachFiles.mkString(" "))
println("-s:" + subjectStr)

まぁ、これでも充分簡潔かもしれないが、
一歩進めて、RubyOptionParserライクなラッパを書いてみた。

package jp.ryugate

class OptionParser {
 import org.apache.commons.cli._
  import scala.collection.mutable._

  val parser = new BasicParser
  val options = new Options
  val blocks = new HashMap[String, String=>Unit]

  def on(opt:String, arg:Boolean, cmt:String)(block:String=>Unit) {
    options.addOption(opt, arg, cmt)
    blocks.update(opt, block)
  }
  
  def parse(args:Array[String]) {
    val commandLine = parser.parse(options, args)
    blocks.foreach {
      case (opt,block) if (commandLine.hasOption(opt)) =>
        block(commandLine.getOptionValue(opt))
    }
  }
}

そうするとさっきのコードはこうなる。

import org.apache.commons.cli._
import jp.ryugate.OptionParser

class NoSuchConfigFileException extends RuntimeException

var testMode = false
var configFilename:String = null
var exAttachFiles = Array[String]()
var subjectStr:String = null

new OptionParser {
  on("t", false, "test mode"        ) { v => testMode = true }
  on("c", true,  "config file"      ) { v => configFilename = v }
  on("a", true,  "extra attach file") { v => exAttachFiles = v.split(",") }
  on("s", true,  "subject"          ) { v => subjectStr = v }

  try {
    parse(args)
    if (configFilename == null) throw new NoSuchConfigFileException
  } catch {
    case e:NoSuchConfigFileException =>
      println("-c は必ず指定してください")
    case e:MissingArgumentException =>
      println(e.getOption + "には引数が必要です。")
    case e:UnrecognizedOptionException =>
      println("不明なオプションです:" + e.getOption)
    case e => 
      throw e
  }
}

println("-t:" + testMode)
println("-c:" + configFilename)
println("-a:" + exAttachFiles.mkString(" "))
println("-s:" + subjectStr)

定義と処理が一体になった分、すこし見やすい。(^^

実行するとこうなる。

$ scalac -cp lib/commons-cli-1.2.jar OptionParser.scala
$ scala -cp lib/commons-cli-1.2.jar:. option.scala -t -c config.xml -a attach1.txt,attach2.csv -s SubjectString
-t:true
-c:config.xml
-a:attach1.txt attach2.csv
-s:SubjectString