diff --git a/src/main/scala/sbt/Main.scala b/src/main/scala/sbt/Main.scala index e06770d1c..4d8f25bc2 100755 --- a/src/main/scala/sbt/Main.scala +++ b/src/main/scala/sbt/Main.scala @@ -141,8 +141,11 @@ class xMain extends xsbti.AppMain def rememberFail(newArgs: List[String]) = failAction.map(f => (FailureHandlerPrefix + f)).toList ::: newArgs def tryOrFail(action: => Trampoline) = try { action } catch { case e: Exception => logCommandError(project.log, e); failed(BuildErrorExitCode) } - def reload(newID: ApplicationID, args: List[String]) = + def reload(args: List[String]) = + { + val newID = new ApplicationID(configuration.provider.id, baseProject.sbtVersion.value) result( new Reboot(project.defScalaVersion.value, rememberCurrent(args), newID, configuration.baseDirectory) ) + } def failed(code: Int) = failAction match { @@ -154,7 +157,7 @@ class xMain extends xsbti.AppMain { case "" :: tail => continue(project, tail, failAction) case (ExitCommand | QuitCommand) :: _ => result( Exit(NormalExitCode) ) - case RebootCommand :: tail => reload( new ApplicationID(configuration.provider.id, baseProject.sbtVersion.value), tail ) + case RebootCommand :: tail => reload( tail ) case InteractiveCommand :: _ => continue(project, prompt(baseProject, project) :: arguments, interactiveContinue) case SpecificBuild(version, action) :: tail => if(Some(version) != baseProject.info.buildScalaVersion) @@ -220,10 +223,14 @@ class xMain extends xsbti.AppMain continue(project, tail, failAction) } - case PHandler(processor, arguments) :: tail => + case PHandler(parsed) :: tail => tryOrFail { - val result =processor(project, arguments) - continue(project, result.insertArguments ::: tail, failAction) + parsed.processor(parsed.label, project, failAction, parsed.arguments) match + { + case s: processor.Success => continue(s.project, s.insertArguments ::: tail, s.onFailure) + case e: processor.Exit => result( Exit(e.code) ) + case r: processor.Reload => reload( r.insertArguments ::: tail ) + } } case action :: tail => diff --git a/src/main/scala/sbt/processor/Handler.scala b/src/main/scala/sbt/processor/Handler.scala index 9933f2e4c..8cd349f98 100644 --- a/src/main/scala/sbt/processor/Handler.scala +++ b/src/main/scala/sbt/processor/Handler.scala @@ -5,10 +5,10 @@ package sbt.processor class Handler(baseProject: Project) extends NotNull { - def unapply(line: String): Option[(Processor, String)] = + def unapply(line: String): Option[ParsedProcessor] = line.split("""\s+""", 2) match { - case Array(GetProcessor(processor), args @ _*) => Some( (processor, args.mkString) ) + case Array(label @ GetProcessor(processor), args @ _*) => Some( new ParsedProcessor(label, processor, args.mkString) ) case _ => None } private object GetProcessor @@ -31,4 +31,5 @@ class Handler(baseProject: Project) extends NotNull lazy val defParser = new DefinitionParser lazy val manager = new ManagerImpl(files, scalaVersion, new Persist(lock, persistLockFile.asFile, defParser), baseProject.log) -} \ No newline at end of file +} +class ParsedProcessor(val label: String, val processor: Processor, val arguments: String) extends NotNull \ No newline at end of file diff --git a/src/main/scala/sbt/processor/Processor.scala b/src/main/scala/sbt/processor/Processor.scala index 883467bd8..5824f40df 100644 --- a/src/main/scala/sbt/processor/Processor.scala +++ b/src/main/scala/sbt/processor/Processor.scala @@ -11,10 +11,28 @@ trait Processor extends NotNull /** Apply this processor's action to the given `project`. * The arguments are passed unparsed as a single String `args`. * The return value optionally provides additional commands to run, such as 'reload'. - * Note: `project` is not necessarily the root project. To get the root project, use `project.rootProject`.*/ - def apply(project: Project, args: String): ProcessorResult + * Note: `project` is not necessarily the root project. To get the root project, use `project.rootProject`. + * The `label` used to call the processor is provided to allow recursing.*/ + def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult } -/** The result of a Processor run. +/** An interface for code that operates on an sbt `Project` but doesn't need to modify command processing.*/ +abstract class BasicProcessor extends Processor +{ + /** Apply this processor's action to the given `project`. + * The arguments are passed unparsed as a single String `args`. + * Note: `project` is not necessarily the root project. To get the root project, use `project.rootProject`.*/ + def apply(project: Project, args: String): Unit + + override final def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult = + { + apply(project, args) + new Success(project, onFailure) + } +} + +/** The result of a Processor run.*/ +sealed trait ProcessorResult extends NotNull +/* Processor success. * `insertArgs` allows the Processor to insert additional commands to run. * These commands are run before pending commands. * @@ -25,7 +43,12 @@ trait Processor extends NotNull * `sbt a cleanCompile b ` * This runs `a`, `cleanCompile`, `clean`, `compile`, and finally `b`. * Commands are processed as if they were entered at the prompt or from the command line.*/ -final class ProcessorResult(insertArgs: String*) extends NotNull +final class Success(val project: Project, val onFailure: Option[String], insertArgs: String*) extends ProcessorResult +{ + val insertArguments = insertArgs.toList +} +final class Exit(val code: Int) extends ProcessorResult +final class Reload(insertArgs: String*) extends ProcessorResult { val insertArguments = insertArgs.toList } diff --git a/src/sbt-test/project/processor/project/build.properties b/src/sbt-test/project/processor/project/build.properties new file mode 100644 index 000000000..c45202545 --- /dev/null +++ b/src/sbt-test/project/processor/project/build.properties @@ -0,0 +1,2 @@ +project.name=Processor Test +project.version=1.0 \ No newline at end of file diff --git a/src/sbt-test/project/processor/project/build/ProcessorProject.scala b/src/sbt-test/project/processor/project/build/ProcessorProject.scala new file mode 100644 index 000000000..7c1170aa4 --- /dev/null +++ b/src/sbt-test/project/processor/project/build/ProcessorProject.scala @@ -0,0 +1,8 @@ +import sbt._ + +class PTest(info: ProjectInfo) extends ProcessorProject(info) +{ + val launcherJar = "org.scala-tools.sbt" % "launcher" % info.app.id.version % "test" + val specs = "org.scala-tools.testing" % "specs" % "1.6.0" % "test" + override def testClasspath = super.testClasspath +++ info.sbtClasspath +} \ No newline at end of file diff --git a/src/sbt-test/project/processor/src/test/scala/ProcessorTest.scala b/src/sbt-test/project/processor/src/test/scala/ProcessorTest.scala new file mode 100644 index 000000000..5be8aed72 --- /dev/null +++ b/src/sbt-test/project/processor/src/test/scala/ProcessorTest.scala @@ -0,0 +1,74 @@ +package ptest + +import sbt._ +import Path.fromFile +import org.specs._ + +class ProcessorTest extends Specification +{ + "basic processor " should { + def success(f: processor.Success => Unit) = successOrFail(basicProcessorResult)(f) + + "succeed" in { + success(_ => ()) + } + "not insert any arguments" in { + success(_.insertArguments must_== Nil) + } + "preserve the fail handler" in { + success(_.onFailure must_== basicFail) + } + } + "full processor " should { + def success(f: processor.Success => Unit) = successOrFail(fullProcessorResult)(f) + "succeed" in { + success(_ => ()) + } + "insert correct arguments" in { + success(_.insertArguments must_== testArgs) + } + "preserve the fail handler" in { + success(_.onFailure must_== testFail) + } + } + + def successOrFail(r: processor.ProcessorResult)(f: processor.Success => Unit) = + r match + { + case s: processor.Success => f(s) + case _ => error("Processor failed: " + r) + } + + def withProject[T](f: Project => T): T = + { + val log = new ConsoleLogger + xsbt.FileUtilities.withTemporaryDirectory { tmp => + val app = xsbt.boot.Launcher.defaultAppProvider(tmp) + val info = new ProjectInfo(tmp, Nil, None)(log, app, None) + val project = new DefaultProject(info) + f(project) + } + } + def basicProcessorResult = + { + var ranBasicArgs: Option[String] = None + val basic = new processor.BasicProcessor { + def apply(p: Project, args: String) = { ranBasicArgs = Some(args) } + } + val result = withProject { project => basic("basic", project, basicFail, basicArgs) } + ranBasicArgs must_== Some(basicArgs) + result + } + def fullProcessorResult = + { + val full = new processor.Processor { + def apply(label: String, p: Project, failAction: Option[String], args: String) = new processor.Success(p, failAction, args.split("""\s+"""): _*) + } + withProject { project => full("full", project, testFail, testArgs.mkString(" ")) } + } + + lazy val testFail = Some("fail") + lazy val testArgs = List("a", "b") + lazy val basicArgs = " a b c " + lazy val basicFail = Some("basic-fail") +} \ No newline at end of file diff --git a/src/sbt-test/project/processor/test b/src/sbt-test/project/processor/test new file mode 100644 index 000000000..5fbb5d887 --- /dev/null +++ b/src/sbt-test/project/processor/test @@ -0,0 +1,2 @@ +> update +> test \ No newline at end of file