Last active
July 3, 2021 21:52
-
-
Save gwenzek/78355526e476e08bb34d to your computer and use it in GitHub Desktop.
Scala parser for programm args
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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