/* NEST (New Scala Test) * Copyright 2007-2013 LAMP/EPFL * @author Paul Phillips */ package scala.tools.nsc package util import scala.util.parsing.combinator._ import scala.util.parsing.input.{ Reader } import scala.util.parsing.input.CharArrayReader.EofCh import scala.collection.mutable.ListBuffer /** A simple command line parser to replace the several different * simple ones spread around trunk. * * XXX Note this has been completely obsolesced by scala.tools.cmd. * I checked it back in as part of rolling partest back a month * rather than go down the rabbit hole of unravelling dependencies. */ trait ParserUtil extends Parsers { protected implicit class ParserPlus[+T](underlying: Parser[T]) { def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } def <~: Parser[T] = (underlying ~! p) ^^ { case a~b => a } } } case class CommandLine( args: List[String], unaryArguments: List[String], binaryArguments: List[String] ) { def this(args: List[String]) = this(args, Nil, Nil) def this(args: Array[String]) = this(args.toList, Nil, Nil) def this(line: String) = this(CommandLineParser tokenize line, Nil, Nil) def withUnaryArgs(xs: List[String]) = copy(unaryArguments = xs) def withBinaryArgs(xs: List[String]) = copy(binaryArguments = xs) def originalArgs = args def assumeBinary = true def enforceArity = true def onlyKnownOptions = false val Terminator = "--" val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption) def errorFn(msg: String) = println(msg) /** argMap is option -> argument (or "" if it is a unary argument) * residualArgs are what is left after removing the options and their args. */ lazy val (argMap, residualArgs) = { val residualBuffer = new ListBuffer[String] def stripQuotes(s: String) = { def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c if (List('"', '\'') exists isQuotedBy) s.tail.init else s } def isValidOption(s: String) = !onlyKnownOptions || (unaryArguments contains s) || (binaryArguments contains s) def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false }) def isUnary(s: String) = isOption(s) && (unaryArguments contains s) def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryArguments contains s)) def unknownOption(opt: String) = errorFn("Option '%s' not recognized.".format(opt)) def missingArg(opt: String, what: String) = errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) def loop(args: List[String]): Map[String, String] = { def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() } if (args.isEmpty) return Map() val hd :: rest = args if (rest.isEmpty) { if (isBinary(hd) && enforceArity) missingArg(hd, "EOF") if (isOption(hd)) mapForUnary(hd) else residual(args) } else if (hd == Terminator) residual(rest) else { val hd1 :: hd2 :: rest = args if (hd2 == Terminator) mapForUnary(hd1) ++ residual(rest) else if (isUnary(hd1)) mapForUnary(hd1) ++ loop(hd2 :: rest) else if (isBinary(hd1)) { // Disabling this check so // --scalacopts "-verbose" works. We can't tell if it's quoted, // the shell does us in. // // if (isOption(hd2) && enforceArity) // missingArg(hd1, hd2) Map(hd1 -> hd2) ++ loop(rest) } else { residual(List(hd1)) ++ loop(hd2 :: rest) } } } (loop(args), residualBuffer map stripQuotes toList) } def isSet(arg: String) = args contains arg def get(arg: String) = argMap get arg def getOrElse(arg: String, orElse: => String) = if (isSet(arg)) apply(arg) else orElse def apply(arg: String) = argMap(arg) override def toString() = "CommandLine(\n%s)\n" format (args map (" " + _ + "\n") mkString) } object CommandLineParser extends RegexParsers with ParserUtil { override def skipWhitespace = false def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) def elemOf(xs: Elem*): Parser[Elem] = elem("elemOf", xs contains _) def escaped(ch: Char): Parser[String] = "\\" + ch def mkQuoted(ch: Char): Parser[String] = ( elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) | failure("Unmatched %s in input." format ch) ) /** Apparently windows can't deal with the quotes sticking around. */ lazy val squoted: Parser[String] = mkQuoted('\'') // ^^ (x => "'%s'" format x) lazy val dquoted: Parser[String] = mkQuoted('"') // ^^ (x => "\"" + x + "\"") lazy val token: Parser[String] = """\S+""".r lazy val argument: Parser[String] = squoted | dquoted | token lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) class ParseException(msg: String) extends RuntimeException(msg) def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) def tokenize(line: String, errorFn: String => Unit): List[String] = { parse(commandLine, line.trim) match { case Success(args, _) => args case NoSuccess(msg, rest) => errorFn(msg) ; Nil } } def apply(line: String) = new CommandLine(tokenize(line)) }