Skip to content

Instantly share code, notes, and snippets.

@mvillafuertem
Forked from gwenzek/ArgsOps.scala
Created July 3, 2021 21:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvillafuertem/8dab8c7b6f82ee3d51db0156f0931109 to your computer and use it in GitHub Desktop.
Save mvillafuertem/8dab8c7b6f82ee3d51db0156f0931109 to your computer and use it in GitHub Desktop.
Scala parser for programm args
import scala.language.implicitConversions
/**
* @author gwenzek
*
*/
class ArgsOps(ops: Map[String, OptionalParam], val args: Array[String]){
def apply(op: String) = ops(op)
}
class OptionalParam(val name: String) {
def asInt() = this match { case IntParam(_, i) => i }
def asString() = this match { case StringParam(_, s) => s }
def asBoolean() = this match { case FlagParam(_, b) => b}
def length() = this match {
case IntParam(_, _) | StringParam(_, _) => 2
case FlagParam(_, _) => 1
}
def toPairs() = name.split("\\|").map( _ -> this )
}
sealed case class IntParam(override val name: String, val value: Int) extends OptionalParam(name)
sealed case class StringParam(override val name: String, val value: String) extends OptionalParam(name)
sealed case class FlagParam(override val name: String, val value: Boolean = false) extends OptionalParam(name)
object ArgsOps {
case class OptionWithoutExpectedParam(name: String) extends Exception
case class UnknownParam(name: String) extends Exception
class ArgsOpsParser(default: Map[String, OptionalParam]){
def parse(args: Array[String]) = {
def parseOne(i: Int, l: List[OptionalParam]): (Int, List[OptionalParam]) = {
if(i == args.length || args(i)(0) != '-') (i, l)
else{
val partialName = args(i)
val op = default.getOrElse(partialName, throw UnknownParam(partialName))
if(i + op.length > args.length) throw OptionWithoutExpectedParam(op.name)
val received : OptionalParam = op match {
case _ : IntParam => try { op.name -> args(i+1).toInt } catch
{case e: java.lang.NumberFormatException => throw OptionWithoutExpectedParam(op.name)}
case _ : StringParam =>
if(args(i+1)(0) == '-') throw OptionWithoutExpectedParam(op.name)
else op.name -> args(i+1)
case _ : FlagParam => FlagParam(op.name, true)
}
parseOne(i+op.length, received :: l)
}
}
val (i, l) = parseOne(0, Nil)
new ArgsOps(default ++ l.flatMap(_.toPairs).toMap, args.slice(i, args.length))
}
def <<|(args: Array[String]) = parse(args)
}
object ArgsOpsParser {
def apply(ops: OptionalParam*) = new ArgsOpsParser(ops.flatMap(_.toPairs).toMap)
}
implicit def asInt(op: OptionalParam) = op match { case IntParam(_, i) => i }
implicit def asString(op: OptionalParam) = op match { case StringParam(_, s) => s }
implicit def asBoolean(op: OptionalParam) = op match { case FlagParam(_, b) => b}
implicit def fromPairSS(kv: (String, String)): OptionalParam= StringParam(kv._1, kv._2)
implicit def fromPairSI(kv: (String, Int)): OptionalParam = IntParam(kv._1, kv._2)
implicit def fromString(k: String): OptionalParam = FlagParam(k)
}
import org.scalatest.FunSuite
import ArgsOps._
val parser = ArgsOpsParser("--someInt|-i" -> 4, "--someFlag|-f", "--someWord|-w" -> "hello")
test("the ArgsOps parser rejects unknow options") {
intercept[UnknownParam](parser <<| Array("--unknown"))
}
test("the ArgsOps parser rejects options without args"){
intercept[OptionWithoutExpectedParam](parser <<| Array("--someInt","--someFlag"))
intercept[OptionWithoutExpectedParam](parser <<| Array("--someInt", "3x"))
intercept[OptionWithoutExpectedParam](parser <<| Array("--someInt"))
intercept[OptionWithoutExpectedParam](parser <<| Array("--someWord", "--someFlag"))
intercept[OptionWithoutExpectedParam](parser <<| Array("--someWord"))
}
test("the ArgsOps parser reads the correct values"){
val argsOps = parser <<| Array("--someInt", "3", "--someFlag", "--someWord", "goodbye")
assert(argsOps("--someInt").asInt == 3)
assert(argsOps("--someFlag").asBoolean == true)
assert(argsOps("--someWord").asString == "goodbye")
}
test("the ArgsOps parser accepts shorted name"){
val argsOps = parser <<| Array("-i", "3", "-f", "-w", "goodbye")
assert(argsOps("-i").asInt == 3)
assert(argsOps("-f").asBoolean == true)
assert(argsOps("-w").asString == "goodbye")
}
test("the ArgsOps parser is coherent between shorted and complet names"){
val argsOps = parser <<| Array("-i", "3", "-f", "-w", "goodbye")
assert(argsOps("--someInt") == argsOps("-i"))
assert(argsOps("--someFlag") == argsOps("-f"))
assert(argsOps("--someWord") == argsOps("-w"))
}
test("the ArgsOps parser keeps the correct default values"){
val argsOps = parser <<| Array("arg")
val someInt : Int = argsOps("--someInt")
val someFlag : Boolean = argsOps("--someFlag")
val someWord : String = argsOps("--someWord")
assert(someInt == 4)
assert(someFlag == false)
assert(someWord == "hello")
}
test("the ArgsOps parser keeps the correct number of arg"){
val argsOps = parser <<| Array("--someInt", "3", "--someFlag", "--someWord", "goodbye", "arg0", "arg1", "arg2")
assert(argsOps.args(0) == "arg0")
assert(argsOps.args(1) == "arg1")
assert(argsOps.args(2) == "arg2")
}
}
import ***.ArgsOps._
object Example {
val parser = ArgsOpsParser("--someInt|-i" -> 4, "--someFlag|-f", "--someWord" -> "hello")
def main(args: Array[String]){
val argsOps = parser <<| args
val someInt : Int = argsOps("--someInt")
val someFlag : Boolean = argsOps("--someFlag")
val someWord : String = argsOps("--someWord")
val otherArgs = argsOps.args
foo(someWord, someInt, someFlag)
}
def foo(s: String, a: Int, insertLine: Boolean) = {
for(i <- 1 to a){
if(insertLine) println(s)
else print(s + ' ')
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment