From c00cc3795399d945ce2bc3a54da80a1403ca60d0 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 19 Nov 2018 08:36:58 -0800 Subject: [PATCH] Do not require leading semicolon for multi command It has long been a frustration of mine that it is necessary to prepend multiple commands with a ';'. In this commit, I relax that restriction. I had to reorder the command definitions so that multi comes before act. This was because if the multi command did not have a leading semicolon, then it would be handled by the action parser before the multi command parser had a shot at it. Sadness ensued. --- .../src/main/scala/sbt/BasicCommands.scala | 6 ++++-- .../src/test/scala/sbt/MultiParserSpec.scala | 17 +++++++++++++++++ main/src/main/scala/sbt/Main.scala | 1 + sbt/src/sbt-test/actions/multi-command/test | 12 ++++++++++++ sbt/src/sbt-test/watch/watch-parser/test | 3 +++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index f647c9478..f5432333e 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -39,7 +39,6 @@ object BasicCommands { ignore, help, completionsCommand, - multi, ifLast, append, setOnFailure, @@ -164,7 +163,10 @@ object BasicCommands { ) def commandParser = state.map(s => (s.combinedParser & cmdPart) | cmdPart).getOrElse(cmdPart) val part = semi.flatMap(_ => matched(commandParser) <~ token(OptSpace)).map(_.trim) - part.+ map (_.toList) + (cmdPart.? ~ part.+).map { + case (Some(h), t) => h.mkString.trim +: t.toList + case (_, t) => t.toList + } } def multiParser(s: State): Parser[List[String]] = multiParserImpl(Some(s)) diff --git a/main-command/src/test/scala/sbt/MultiParserSpec.scala b/main-command/src/test/scala/sbt/MultiParserSpec.scala index 06a30a179..ec18b1c58 100644 --- a/main-command/src/test/scala/sbt/MultiParserSpec.scala +++ b/main-command/src/test/scala/sbt/MultiParserSpec.scala @@ -14,6 +14,7 @@ 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 + def parseEither: Either[String, Seq[String]] = Parser.parse(s, parser) } } import MultiParserSpec._ @@ -45,4 +46,20 @@ class MultiParserSpec extends FlatSpec with Matchers { """; setStringValue "foo;bar"; checkStringValue "foo;bar"""".parse shouldBe Seq("""setStringValue "foo;bar"""", """checkStringValue "foo;bar"""") } + it should "parse commands without leading ';'" in { + "setStringValue foo; setStringValue bar".parse shouldBe Seq( + "setStringValue foo", + "setStringValue bar" + ) + "foo; bar".parse shouldBe Seq("foo", "bar") + "foo bar ;bar".parse shouldBe Seq("foo bar", "bar") + "foo \"a;b\"; bar".parse shouldBe Seq("foo \"a;b\"", "bar") + " foo ; bar \"b;c\"".parse shouldBe Seq("foo", "bar \"b;c\"") + } + it should "not parse single commands without leading ';'" in { + "foo".parseEither shouldBe Left("Expected ';'\nfoo\n ^") + "foo bar baz".parseEither shouldBe Left("Expected ';'\nfoo bar baz\n ^") + "foo bar baz;".parseEither shouldBe + Left("Expected not ';'\nExpected '\"'\nfoo bar baz;\n ^") + } } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 03dd44844..23c4b232e 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -241,6 +241,7 @@ object BuiltinCommands { export, boot, initialize, + BasicCommands.multi, act, continuous, flushFileTreeRepository diff --git a/sbt/src/sbt-test/actions/multi-command/test b/sbt/src/sbt-test/actions/multi-command/test index a98fad58c..8bc828c03 100644 --- a/sbt/src/sbt-test/actions/multi-command/test +++ b/sbt/src/sbt-test/actions/multi-command/test @@ -7,3 +7,15 @@ > checkStringValue bar > ; setStringValue foo; setStringValue bar; setStringValue baz; checkStringValue baz + +> setStringValue foo; setStringValue bar + +> checkStringValue bar + +> setStringValue foo; setStringValue bar; setStringValue baz + +> checkStringValue baz + +-> setStringValue foo; taskThatFails; setStringValue bar + +> checkStringValue foo diff --git a/sbt/src/sbt-test/watch/watch-parser/test b/sbt/src/sbt-test/watch/watch-parser/test index f14bef5e1..63a58eb8a 100644 --- a/sbt/src/sbt-test/watch/watch-parser/test +++ b/sbt/src/sbt-test/watch/watch-parser/test @@ -8,6 +8,9 @@ > ~; setStringValue foo; setStringValue bar; checkStringValue bar +# no leading semicolon +> ~ setStringValue foo; setStringValue bar; checkStringValue bar + > ~ setStringValue foo > checkStringValue foo