Refactor multi parser

Prior to this commit, there was no unit testing of the parser for
multiple commands. I wanted to make some improvements to the parser, so
I reworked the implementation to be testable. This change also allows
the multiParserImpl method to be shared with Watched.watch, which I will
also update in a subsequent commit.

There also were no explicit scripted tests for multiple commands, so I
added one that I will augment in later commits.
This commit is contained in:
Ethan Atkins 2018-11-19 07:55:14 -08:00
parent 93d77d593f
commit 4281972f1a
5 changed files with 82 additions and 2 deletions

View File

@ -154,15 +154,18 @@ object BasicCommands {
state
}
def multiParser(s: State): Parser[List[String]] = {
private[sbt] def multiParserImpl(state: Option[State]): Parser[List[String]] = {
val nonSemi = token(charClass(_ != ';', "not ';'").+, hide = const(true))
val semi = token(';' ~> OptSpace)
def commandParser = state.map(s => (s.combinedParser & nonSemi) | nonSemi).getOrElse(nonSemi)
val part = semi flatMap (
_ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace)
_ => matched(commandParser) <~ token(OptSpace)
)
(part map (_.trim)).+ map (_.toList)
}
def multiParser(s: State): Parser[List[String]] = multiParserImpl(Some(s))
def multiApplied(s: State): Parser[() => State] =
Command.applyEffect(multiParser(s))(_ ::: s)

View File

@ -0,0 +1,36 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
import org.scalatest.{ FlatSpec, Matchers }
import sbt.internal.util.complete.Parser
object MultiParserSpec {
val parser: Parser[Seq[String]] = BasicCommands.multiParserImpl(None)
implicit class StringOps(val s: String) {
def parse: Seq[String] = Parser.parse(s, parser).right.get
}
}
import MultiParserSpec._
class MultiParserSpec extends FlatSpec with Matchers {
"parsing" should "parse single commands" in {
";foo".parse shouldBe Seq("foo")
"; foo".parse shouldBe Seq("foo")
}
it should "parse multiple commands" in {
";foo;bar".parse shouldBe Seq("foo", "bar")
}
it should "parse single command with leading spaces" in {
"; foo".parse shouldBe Seq("foo")
}
it should "parse multiple commands with leading spaces" in {
"; foo;bar".parse shouldBe Seq("foo", "bar")
"; foo; bar".parse shouldBe Seq("foo", "bar")
";foo; bar".parse shouldBe Seq("foo", "bar")
}
}

View File

@ -0,0 +1,14 @@
import Build._
organization := "sbt"
name := "scripted-multi-command-parser"
setStringValue := setStringValueImpl.evaluated
checkStringValue := checkStringValueImpl.evaluated
taskThatFails := {
throw new IllegalArgumentException("")
()
}

View File

@ -0,0 +1,18 @@
import sbt._
object Build {
private[this] var string: String = ""
private[this] val stringFile = file("string.txt")
val setStringValue = inputKey[Unit]("set a global string to a value")
val checkStringValue = inputKey[Unit]("check the value of a global")
val taskThatFails = taskKey[Unit]("this should fail")
def setStringValueImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask {
string = Def.spaceDelimited().parsed.mkString(" ").trim
IO.write(stringFile, string)
}
def checkStringValueImpl: Def.Initialize[InputTask[Unit]] = Def.inputTask {
val actual = Def.spaceDelimited().parsed.mkString(" ").trim
assert(string == actual)
assert(IO.read(stringFile) == string)
}
}

View File

@ -0,0 +1,9 @@
> ; setStringValue baz
> ; checkStringValue baz
> ; setStringValue foo; setStringValue bar
> checkStringValue bar
> ; setStringValue foo; setStringValue bar; setStringValue baz; checkStringValue baz