Skip to content

Instantly share code, notes, and snippets.

@friedbrice
Created June 27, 2017 08:32
Show Gist options
  • Select an option

  • Save friedbrice/33302de9af107b809c19da2bac83baab to your computer and use it in GitHub Desktop.

Select an option

Save friedbrice/33302de9af107b809c19da2bac83baab to your computer and use it in GitHub Desktop.
Zero-dependency Scala Command Line Parser
object CommandLineDemo extends App {
val clargs: CLArgs = CLArgs(args)
val all: Option[Unit] = clargs.flag('a', "all")
val list: Option[Unit] = clargs.shortFlag('l')
val ctl: Option[Unit] = clargs.longFlag("show-control-chars")
val tab: Option[String] = clargs.value('T', "tabsize")
println(s"$all-$list-$ctl-$tab")
}
class CLArgs(rawArgs: Array[String]) {
import CLArgs._
private val args: Args = parse(rawArgs)
def flag(short: Char, long: String): Option[Unit] =
shortFlag(short) \/ longFlag(long)
def shortFlag(short: Char): Option[Unit] =
checkIf(args.shortFlags.contains(short))
def longFlag(long: String): Option[Unit] =
checkIf(args.longFlags.contains(long))
def value(short: Char, long: String): Option[String] =
shortValue(short) \/ longValue(long)
def shortValue(short: Char): Option[String] =
args.shortValues.get(short)
def longValue(long: String): Option[String] =
args.longValues.get(long)
}
object CLArgs {
def apply(rawArgs: Array[String]): CLArgs = new CLArgs(rawArgs)
private sealed trait Token extends Product with Serializable
private case class Short(c: Char) extends Token
private case class Long(s: String) extends Token
private case class Value(v: String) extends Token
private case class Args(
shortFlags: List[Char],
longFlags: List[String],
shortValues: Map[Char, String],
longValues: Map[String, String]
)
private val emptyArgs = Args(List.empty, List.empty, Map.empty, Map.empty)
private def tokenize(args: List[String]): List[Token] =
args.flatMap(arg => arg.split("=")).flatMap({
case s if s.startsWith("--") => List(Long(s.substring(2)))
case s if s.startsWith("-") => s.substring(1).toList.map(Short)
case s => List(Value(s))
})
private def analyze(tokens: List[Token]): Args = {
val pairs = if (tokens.isEmpty) Nil else tokens.zip(tokens.tail)
pairs.foldLeft(emptyArgs) { (args, pair) =>
(pair._1, pair._2) match {
case (Short(c), Value(v)) =>
args.copy(shortValues = args.shortValues.updated(c, v))
case (Short(c), _) =>
args.copy(shortFlags = c +: args.shortFlags)
case (Long(s), Value(v)) =>
args.copy(longValues = args.longValues.updated(s, v))
case (Long(s), _) =>
args.copy(longFlags = s +: args.longFlags)
case _ => args
}
}
}
private def parse(args: Array[String]): Args = analyze(tokenize(args.toList))
private implicit class OptionOps[A](self: Option[A]) {
def \/[A2 >: A](other: Option[A2]): Option[A2] = self.fold(other)(_ => self)
}
private def checkIf(p: Boolean): Option[Unit] = if (p) Some(()) else None
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment