mirror of https://github.com/sbt/sbt.git
Make multi command parser work with string literals
Presently the multi command parser doesn't work correctly if one of the commands includes a string literal. For example, suppose that there is an input task defined name "bash" that shells out and runs the input. Then the following does not work with the current multi command parser: ; bash "rm target/classes/Foo.class; touch src/main/scala/Foo.scala"; comple Note that this is a real use case that has caused me issues in the past. The problem is that the semicolon inside of the quote gets interpreted as a command separator token. To fix this, I rework the parser so that it consumes string literals and doesn't modify them. By using StringEscapable, I allow the string to contain quotation marks itself. I couldn't write a scripted test for this because in a command like `; foo "bar"; baz`, the quotes around bar seem to get stripped. This could be fixed by adding an alternative to StringEscapable that matches an escaped string, but that is more work than I'm willing to do right now.
This commit is contained in:
parent
05e3a8609b
commit
51d986d751
|
|
@ -155,13 +155,16 @@ object BasicCommands {
|
|||
}
|
||||
|
||||
private[sbt] def multiParserImpl(state: Option[State]): Parser[List[String]] = {
|
||||
val nonSemi = token(charClass(_ != ';', "not ';'").+, hide = const(true))
|
||||
val nonSemi = charClass(_ != ';', "not ';'")
|
||||
val semi = token(';' ~> OptSpace)
|
||||
def commandParser = state.map(s => (s.combinedParser & nonSemi) | nonSemi).getOrElse(nonSemi)
|
||||
val part = semi flatMap (
|
||||
_ => matched(commandParser) <~ token(OptSpace)
|
||||
val nonQuote = charClass(_ != '"', label = "not '\"'")
|
||||
val cmdPart = token(
|
||||
((nonSemi & nonQuote).map(_.toString) | StringEscapable.map(c => s""""$c"""")).+,
|
||||
hide = const(true)
|
||||
)
|
||||
(part map (_.trim)).+ map (_.toList)
|
||||
def commandParser = state.map(s => (s.combinedParser & cmdPart) | cmdPart).getOrElse(cmdPart)
|
||||
val part = semi.flatMap(_ => matched(commandParser) <~ token(OptSpace)).map(_.trim)
|
||||
part.+ map (_.toList)
|
||||
}
|
||||
|
||||
def multiParser(s: State): Parser[List[String]] = multiParserImpl(Some(s))
|
||||
|
|
|
|||
|
|
@ -33,4 +33,16 @@ class MultiParserSpec extends FlatSpec with Matchers {
|
|||
"; foo; bar".parse shouldBe Seq("foo", "bar")
|
||||
";foo; bar".parse shouldBe Seq("foo", "bar")
|
||||
}
|
||||
it should "parse command with string literal" in {
|
||||
"; foo \"barbaz\"".parse shouldBe Seq("foo \"barbaz\"")
|
||||
"; foo \"bar;baz\"".parse shouldBe Seq("foo \"bar;baz\"")
|
||||
"; foo \"barbaz\"; bar".parse shouldBe Seq("foo \"barbaz\"", "bar")
|
||||
"; foo \"barbaz\"; bar \"blah\"".parse shouldBe Seq("foo \"barbaz\"", "bar \"blah\"")
|
||||
"; foo \"bar;baz\"; bar".parse shouldBe Seq("foo \"bar;baz\"", "bar")
|
||||
"; foo \"bar;baz\"; bar \"buzz\"".parse shouldBe Seq("foo \"bar;baz\"", "bar \"buzz\"")
|
||||
"; foo \"bar;baz\"; bar \"buzz;two\"".parse shouldBe Seq("foo \"bar;baz\"", "bar \"buzz;two\"")
|
||||
"""; foo "bar;\"baz\""; bar""".parse shouldBe Seq("""foo "bar;\"baz\""""", "bar")
|
||||
"""; setStringValue "foo;bar"; checkStringValue "foo;bar"""".parse shouldBe
|
||||
Seq("""setStringValue "foo;bar"""", """checkStringValue "foo;bar"""")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue