Relax strict commands

The recent changes to make the multi parser strict broke any multi
command, or alias, where the multi command contained a command or task
that was not yet defined, but was possibly added by reload. This was
reported as #4869. I had had to work around this issue in ScriptedTests
by running `reload` and `setUpScripted` separately instead of as a multi
command. This workaround doesn't work for aliasing boot, which has been
a recommended approach by Mark Harrah since 2011.

To fix this, I relax the strict parser. We don't require that the parser
be valid to create a multi command string. In the multiApplied state
transformation, however, we validate all of the commands up to 'reload'.
Since there is no way to validate any commands to the right of 'reload,
we optimistically allow those commands to run.

So long as there is no 'reload' in the multi commands, all of the
commands will be validated.
This commit is contained in:
Ethan Atkins 2019-07-16 13:16:03 -07:00
parent d4df289f2d
commit a93d9e77ad
2 changed files with 22 additions and 15 deletions

View File

@ -158,11 +158,12 @@ object BasicCommands {
val semi = token(OptSpace ~> ';' ~> OptSpace)
val nonDelim = charClass(c => c != '"' && c != '{' && c != '}', label = "not '\"', '{', '}'")
val components = ((nonSemi & nonDelim) | StringEscapable | braces('{', '}')).+
val cmdPart = matched(components)
val cmdPart = matched(components).examples()
val completionParser: Option[Parser[String]] =
state.map(s => OptSpace ~> matched(s.nonMultiParser) <~ OptSpace)
val cmdParser = completionParser.map(sp => sp & cmdPart).getOrElse(cmdPart).map(_.trim)
val cmdParser =
completionParser.map(sp => (sp & cmdPart) | cmdPart).getOrElse(cmdPart).map(_.trim)
val multiCmdParser: Parser[String] = semi ~> cmdParser
/*
* There are two cases that need to be handled separately:
@ -187,7 +188,14 @@ object BasicCommands {
def multiParser(s: State): Parser[List[String]] = multiParserImpl(Some(s))
def multiApplied(state: State): Parser[() => State] =
def multiApplied(state: State): Parser[() => State] = {
def generateCommands(commands: List[String]): State =
commands
.takeWhile(_ != "reload")
.collectFirst {
case c if Parser.parse(c, state.combinedParser).isLeft => c :: state
}
.getOrElse(commands ::: state)
Command.applyEffect(multiParserImpl(Some(state))) {
// the (@ _ :: _) ensures tail length >= 1.
case commands @ first :: (tail @ _ :: _) =>
@ -230,10 +238,11 @@ object BasicCommands {
}
}.headOption match {
case Some(s) => s()
case _ => commands ::: state
case _ => generateCommands(commands)
}
case commands => commands ::: state
case commands => generateCommands(commands)
}
}
val multi: Command =
Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed), Multi)

View File

@ -328,20 +328,18 @@ final class ScriptedTests(
IO.write(tempTestDir / "project" / "InstrumentScripted.scala", pluginImplementation)
def sbtHandlerError = sys error "Missing sbt handler. Scripted is misconfigured."
val sbtHandler = handlers.getOrElse('>', sbtHandlerError)
val commandsToRun = ";reload;setUpScripted"
val statement = Statement(commandsToRun, Nil, successExpected = true, line = -1)
// Run reload inside the hook to reuse error handling for pending tests
val wrapHook = (file: File) => {
preHook(file)
Seq("reload", "setUpScripted")
.map(Statement(_, Nil, successExpected = true, line = -1))
.foreach { statement =>
try runner.processStatement(sbtHandler, statement, states)
catch {
case t: Throwable =>
val newMsg = "Reload for scripted batch execution failed."
throw new TestException(statement, newMsg, t)
}
}
try runner.processStatement(sbtHandler, statement, states)
catch {
case t: Throwable =>
val newMsg = "Reload for scripted batch execution failed."
throw new TestException(statement, newMsg, t)
}
}
commonRunTest(label, tempTestDir, wrapHook, handlers, runner, states, buffer)