From 4281972f1a3232d1c2eb76e1197bbb02a142993a Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 19 Nov 2018 07:55:14 -0800 Subject: [PATCH] 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. --- .../src/main/scala/sbt/BasicCommands.scala | 7 ++-- .../src/test/scala/sbt/MultiParserSpec.scala | 36 +++++++++++++++++++ .../sbt-test/actions/multi-command/build.sbt | 14 ++++++++ .../actions/multi-command/project/Build.scala | 18 ++++++++++ sbt/src/sbt-test/actions/multi-command/test | 9 +++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 main-command/src/test/scala/sbt/MultiParserSpec.scala create mode 100644 sbt/src/sbt-test/actions/multi-command/build.sbt create mode 100644 sbt/src/sbt-test/actions/multi-command/project/Build.scala create mode 100644 sbt/src/sbt-test/actions/multi-command/test diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 0e8c51a41..8d4b9c34d 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -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) diff --git a/main-command/src/test/scala/sbt/MultiParserSpec.scala b/main-command/src/test/scala/sbt/MultiParserSpec.scala new file mode 100644 index 000000000..e29eca571 --- /dev/null +++ b/main-command/src/test/scala/sbt/MultiParserSpec.scala @@ -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") + } +} diff --git a/sbt/src/sbt-test/actions/multi-command/build.sbt b/sbt/src/sbt-test/actions/multi-command/build.sbt new file mode 100644 index 000000000..0aff2d364 --- /dev/null +++ b/sbt/src/sbt-test/actions/multi-command/build.sbt @@ -0,0 +1,14 @@ +import Build._ + +organization := "sbt" + +name := "scripted-multi-command-parser" + +setStringValue := setStringValueImpl.evaluated + +checkStringValue := checkStringValueImpl.evaluated + +taskThatFails := { + throw new IllegalArgumentException("") + () +} diff --git a/sbt/src/sbt-test/actions/multi-command/project/Build.scala b/sbt/src/sbt-test/actions/multi-command/project/Build.scala new file mode 100644 index 000000000..5190dc191 --- /dev/null +++ b/sbt/src/sbt-test/actions/multi-command/project/Build.scala @@ -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) + } +} diff --git a/sbt/src/sbt-test/actions/multi-command/test b/sbt/src/sbt-test/actions/multi-command/test new file mode 100644 index 000000000..a98fad58c --- /dev/null +++ b/sbt/src/sbt-test/actions/multi-command/test @@ -0,0 +1,9 @@ +> ; setStringValue baz + +> ; checkStringValue baz + +> ; setStringValue foo; setStringValue bar + +> checkStringValue bar + +> ; setStringValue foo; setStringValue bar; setStringValue baz; checkStringValue baz