Split Processor into Processor/BasicProcessor:

* Processor provides a high level of integration with command processing.
 * BasicProcessor operates on a Project but does not affect command processing.
This commit is contained in:
Mark Harrah 2010-03-19 19:21:40 -04:00
parent df6d83911a
commit de8af817ac
7 changed files with 129 additions and 12 deletions

View File

@ -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 =>

View File

@ -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)
}
}
class ParsedProcessor(val label: String, val processor: Processor, val arguments: String) extends NotNull

View File

@ -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
}

View File

@ -0,0 +1,2 @@
project.name=Processor Test
project.version=1.0

View File

@ -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
}

View File

@ -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")
}

View File

@ -0,0 +1,2 @@
> update
> test