part I of revised approach to commands/projects

no privileged project member of State
no separation of Command and Apply, so no pre-filtering on State
use entries in State attributes map instead of mixing in traits to project object:
  HistoryPath, Logger, Analysis, Navigate, Watch, TaskedKey
rework Navigation to be standalone instead of mixin
This commit is contained in:
Mark Harrah 2010-12-19 12:03:10 -05:00
parent 20a5e3b3c7
commit 6960d24158
9 changed files with 197 additions and 228 deletions

View File

@ -3,18 +3,14 @@
*/
package sbt
import Execute.NodeView
import Completions.noCompletions
import Execute.NodeView
import java.io.File
import Function.untupled
trait Command
{
def applies: State => Option[Apply]
}
trait Apply
{
def help: Seq[Help]
def run: Input => Option[State]
def complete: Input => Completions
def help: State => Seq[Help]
def run: (Input, State) => Option[State]
}
trait Help
{
@ -23,87 +19,61 @@ trait Help
}
object Help
{
def apply(briefHelp: (String, String), detailedHelp: (Set[String], String) = (Set.empty, "") ): Help = new Help { def detail = detailedHelp; def brief = briefHelp }
}
trait Completions
{
def candidates: Seq[String]
def position: Int
}
object Completions
{
implicit def seqToCompletions(s: Seq[String]): Completions = apply(s : _*)
def apply(s: String*): Completions = new Completions { def candidates = s; def position = 0 }
def noCompletions: PartialFunction[Input, Completions] = { case _ => Completions() }
def apply(briefHelp: (String, String), detailedHelp: (Set[String], String) = (Set.empty, "") ): Help =
new Help { def detail = detailedHelp; def brief = briefHelp }
}
object Command
{
def univ(f: State => Apply): Command = direct(s => Some(f(s)))
def direct(f: State => Option[Apply]): Command =
new Command { def applies = f }
def apply(f: PartialFunction[State, Apply]): Command =
direct(f.lift)
val Logged = AttributeKey[Logger]("log")
val HistoryPath = AttributeKey[Option[File]]("history")
val Analysis = AttributeKey[inc.Analysis]("analysis")
val Watch = AttributeKey[Watched]("continuous-watch")
val Navigate = AttributeKey[Navigation]("navigation")
val TaskedKey = AttributeKey[Tasked]("tasked")
def direct(h: Help*)(r: (Input, State) => Option[State]): Command =
new Command { def help = _ => h; def run = r }
def apply(h: Help*)(r: PartialFunction[(Input, State), State]): Command =
direct(h : _*)(untupled(r.lift))
def simple(name: String, help: Help*)(f: (Input, State) => State): Command =
univ( Apply.simple(name, help : _*)(f) )
def simple(name: String, brief: (String, String), detail: String)(f: (Input, State) => State): Command =
univ( Apply.simple(name, brief, detail)(f) )
}
object Apply
{
def direct(h: Help*)(c: Input => Completions = noCompletions )(r: Input => Option[State]): Apply =
new Apply { def help = h; def run = r; def complete = c }
def apply(h: Help*)(r: PartialFunction[Input, State]): Apply =
direct(h : _*)( noCompletions )(r.lift)
/* Currently problematic for apply( Seq(..): _*)() (...)
* TODO: report bug and uncomment when fixed
def apply(h: Help*)(complete: PartialFunction[Input, Completions] = noCompletions )(r: PartialFunction[Input, State]): Apply =
direct(h : _*)( complete orElse noCompletions )(r.lift)*/
def simple(name: String, brief: (String, String), detail: String)(f: (Input, State) => State): State => Apply =
{
val h = Help(brief, (Set(name), detail) )
simple(name, h)(f)
}
def simple(name: String, help: Help*)(f: (Input, State) => State): State => Apply =
s => Apply( help: _* ){ case in if name == in.name => f( in, s) }
def simple(name: String, help: Help*)(f: (Input, State) => State): Command =
Command( help: _* ){ case (in, s) if name == in.name => f( in, s) }
}
/*
final case class ProjectSpace(
projects: Map[String, Project],
/* sessionPrepend: Seq[Setting],
sessionAppend: Seq[Setting],
data: Settings,*/
external: Map[String, Project]
// eval: Option[Eval]
) extends Identity*/
trait Logged
{
def log: Logger
}
trait HistoryEnabled
{
def historyPath: Option[Path]
}
trait Named
{
def name: String
}
trait Navigation[Project]
trait Navigation
{
def parentProject: Option[Project]
type Project <: AnyRef
def self: Project
def initialProject(s: State): Project
def projectClosure(s: State): Seq[Project]
def rootProject: Project
}
trait Member[Node <: Member[Node]]
{
def navigation: Navigation[Node]
def name: String
def parent: Option[Navigation]
def select(s: State): State
def selected: Navigation
def initial: Navigation
def closure: Seq[Navigation]
def root: Navigation
}
trait Tasked
{
type Task[T] <: AnyRef
def act(in: Input, state: State): Option[(Task[State], NodeView[Task])]
def help: Seq[Help]
}
trait TaskSetup
{
def maxThreads = Runtime.getRuntime.availableProcessors
def checkCycles = false
}

View File

@ -11,12 +11,11 @@ import Path._
object CommandSupport
{
def logger(s: State) = s match {
case State(p: Logged) => p.log
case _ => ConsoleLogger() //TODO: add a default logger to State
}
def notReadable(files: Seq[File]): Seq[File] = files filter { !_.canRead }
def readable(files: Seq[File]): Seq[File] = files filter { _.canRead }
def logger(s: State) = s get Command.Logged getOrElse ConsoleLogger()
private def canRead = (_: File).canRead
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
def readable(files: Seq[File]): Seq[File] = files filter canRead
def sbtRCs(s: State): Seq[File] =
(Path.userHome / sbtrc) ::
(s.baseDir / sbtrc asFile) ::

View File

@ -7,6 +7,7 @@ import Execute.NodeView
import complete.HistoryCommands
import HistoryCommands.{Start => HistoryPrefix}
import sbt.build.{AggressiveCompile, Auto, Build, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad}
import Command.{Analysis,HistoryPath,Logged,Navigate,TaskedKey,Watch}
import scala.annotation.tailrec
import scala.collection.JavaConversions._
import Path._
@ -22,9 +23,10 @@ class xMain extends xsbti.AppMain
import CommandSupport.{DefaultsCommand, InitCommand}
val initialCommandDefs = Seq(initialize, defaults)
val commands = DefaultsCommand :: InitCommand :: configuration.arguments.map(_.trim).toList
val state = State( () )( configuration, initialCommandDefs, Set.empty, None, commands, AttributeMap.empty, Next.Continue )
val state = State( configuration, initialCommandDefs, Set.empty, None, commands, initialAttributes, Next.Continue )
run(state)
}
def initialAttributes = AttributeMap.empty.put(Logged, ConsoleLogger())
@tailrec final def run(state: State): xsbti.MainResult =
{
@ -48,7 +50,7 @@ class xMain extends xsbti.AppMain
def process(command: String, state: State): State =
{
val in = Input(command, None)
Commands.applicable(state).flatMap( _.run(in) ).headOption.getOrElse {
Commands.applicable(state).flatMap( _.run(in, state) ).headOption.getOrElse {
if(command.isEmpty) state
else {
System.err.println("Unknown command '" + command + "'")
@ -61,22 +63,21 @@ class xMain extends xsbti.AppMain
import CommandSupport._
object Commands
{
def DefaultCommands = Seq(ignore, help, reload, read, history, continuous, exit, load, loadCommands, loadProject, compile, discover,
def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover,
projects, project, setOnFailure, ifLast, multi, shell, alias, append, act)
def ignore = nothing(Set(FailureWall))
def nothing(ignore: Set[String]) = Command.univ { s => Apply(){ case in if ignore(in.line) => s } }
def nothing(ignore: Set[String]) = Command(){ case (in, s) if ignore(in.line) => s }
def applicable(state: State): Stream[Apply] =
state.processors.toStream.flatMap(_.applies(state) )
def applicable(state: State): Stream[Command] = state.processors.toStream
def detail(selected: Iterable[String])(h: Help): Option[String] =
h.detail match { case (commands, value) => if( selected exists commands ) Some(value) else None }
def help = Command.simple(HelpCommand, helpBrief, helpDetailed) { (in, s) =>
val h = applicable(s).flatMap(_.help)
val h = applicable(s).flatMap(_.help(s))
val argStr = (in.line stripPrefix HelpCommand).trim
val message =
@ -97,11 +98,11 @@ object Commands
}
def shell = Command.simple(Shell, ShellBrief, ShellDetailed) { (in, s) =>
val historyPath = s.project match { case he: HistoryEnabled => he.historyPath; case _ => Some(s.baseDir / ".history") }
val reader = new LazyJLineReader(historyPath, new LazyCompletor(completor(s)))
val historyPath = (s get HistoryPath) getOrElse Some((s.baseDir / ".history").asFile)
val reader = new LazyJLineReader(historyPath)
val line = reader.readLine("> ")
line match {
case Some(line) => s.copy()(onFailure = Some(Shell), commands = line +: Shell +: s.commands)
case Some(line) => s.copy(onFailure = Some(Shell), commands = line +: Shell +: s.commands)
case None => s
}
}
@ -114,11 +115,11 @@ object Commands
if(s.commands.isEmpty) in.arguments :: s else s
}
def append = Command.simple(Append, AppendLastBrief, AppendLastDetailed) { (in, s) =>
s.copy()(commands = s.commands :+ in.arguments)
s.copy(commands = s.commands :+ in.arguments)
}
def setOnFailure = Command.simple(OnFailure, OnFailureBrief, OnFailureDetailed) { (in, s) =>
s.copy()(onFailure = Some(in.arguments))
s.copy(onFailure = Some(in.arguments))
}
def reload = Command.simple(ReloadCommand, ReloadBrief, ReloadDetailed) { (in, s) =>
@ -141,7 +142,7 @@ object Commands
val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match
{
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy()(onFailure = Some(ReadCommand + " " + (-port)))
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
case None =>
System.err.println("Connection closed.")
s.fail
@ -172,138 +173,126 @@ object Commands
}
}
def continuous = Command { case s @ State(p: Project with Watched) =>
Apply( Help(continuousBriefHelp) ) {
case in if in.line startsWith ContinuousExecutePrefix => Watched.executeContinuously(p, s, in)
def continuous =
Command( Help(continuousBriefHelp) ) { case (in, s) if in.line startsWith ContinuousExecutePrefix =>
withAttribute(s, Watch, "Continuous execution not configured.") { w =>
Watched.executeContinuously(w, s, in)
}
}
}
def history = Command { case s @ State(p: HistoryEnabled) =>
Apply( historyHelp: _* ) {
case in if in.line startsWith("!") =>
val logError: (String => Unit) = p match { case l: Logged => (s: String) => l.log.error(s) ; case _ => System.err.println _ }
HistoryCommands(in.line.substring(HistoryPrefix.length).trim, p.historyPath, 500/*JLine.MaxHistorySize*/, logError) match
{
case Some(commands) =>
commands.foreach(println) //printing is more appropriate than logging
(commands ::: s).continue
case None => s.fail
}
def history = Command( historyHelp: _* ) { case (in, s) if in.line startsWith "!" =>
val logError = (msg: String) => CommandSupport.logger(s).error(msg)
HistoryCommands(in.line.substring(HistoryPrefix.length).trim, (s get HistoryPath) getOrElse None, 500/*JLine.MaxHistorySize*/, logError) match
{
case Some(commands) =>
commands.foreach(println) //printing is more appropriate than logging
(commands ::: s).continue
case None => s.fail
}
}
def indent(withStar: Boolean) = if(withStar) "\t*" else "\t"
def listProject(p: Named, current: Boolean, log: Logger) = printProject( indent(current), p, log)
def printProject(prefix: String, p: Named, log: Logger) = log.info(prefix + p.name)
def listProject(name: String, current: Boolean, log: Logger) = log.info( indent(current) + name )
def projects = Command { case s @ State(d: Member[_]) =>
Apply.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) =>
val log = logger(s)
d.navigation.projectClosure(s).foreach { case n: Named => listProject(n, d eq n, log); case _ => () }
def projects = Command.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) =>
val log = logger(s)
withNavigation(s) { nav =>
nav.closure.foreach { p => listProject(p.name, nav.self eq p.self, log) }
s
}(s)
}
}
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
(s get key) match {
case None => logger(s).error(ifMissing); s.fail
case Some(nav) => f(nav)
}
def withNavigation(s: State)(f: Navigation => State): State = withAttribute(s, Navigate, "No navigation configured.")(f)
def project = Command { case s @ State(d: Member[_] with Named) =>
Apply.simple(ProjectCommand, projectBrief, projectDetailed ) { (in,s) =>
def project = Command.simple(ProjectCommand, projectBrief, projectDetailed ) { (in,s) =>
withNavigation(s) { nav =>
val to = in.arguments
if(to.isEmpty)
{
logger(s).info(d.name)
logger(s).info(nav.name)
s
}
else if(to == "/")
setProject(d.navigation.initialProject(s), s)
nav.root.select(s)
else if(to.forall(_ == '.'))
if(to.length > 1) gotoParent(to.length - 1, d, s) else s
if(to.length > 1) gotoParent(to.length - 1, nav, s) else s
else
{
d.navigation.projectClosure(s).find { case n: Named => n.name == to; case _ => false } match
nav.closure.find { _.name == to } match
{
case Some(np) => setProject(np, s)
case Some(np) => np.select(s)
case None => logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects)."); s.fail
}
}
}(s)
}
}
@tailrec def gotoParent[Node <: Member[Node]](n: Int, base: Member[Node], s: State): State =
base.navigation.parentProject match
@tailrec def gotoParent(n: Int, nav: Navigation, s: State): State =
nav.parent match
{
case Some(pp) => if(n <= 1) setProject(pp, s) else gotoParent(n-1, pp, s)
case None => if(s.project == base) s else setProject(base, s)
case Some(pp) => if(n <= 1) pp.select(s) else gotoParent(n-1, pp, s)
case None => nav.select(s)
}
def setProject(np: AnyRef, s: State): State =
{
np match { case n: Named =>
logger(s).info("Set current project to " + n.name)
}
s.copy(np)()
}
def exit = Command { case s => Apply( Help(exitBrief) ) {
case in if TerminateActions contains in.line =>
def exit = Command( Help(exitBrief) ) {
case (in, s) if TerminateActions contains in.line =>
runExitHooks(s).exit(true)
}
def act = new Command {
def help = s => (s get TaskedKey).toSeq.flatMap { _.help }
def run = (in, s) => (s get TaskedKey) flatMap { p =>
import p.{checkCycles, maxThreads}
for( (task, taskToNode) <- p.act(in, s)) yield
processResult(runTask(task, checkCycles, maxThreads)(taskToNode), s, s.fail)
}
}
def act = Command { case s @ State(p: Tasked) =>
new Apply {
def help = p.help
def complete = in => Completions()
def run = in => {
val (checkCycles, maxThreads) = p match {
case c: TaskSetup => (c.checkCycles, c.maxThreads)
case _ => (false, Runtime.getRuntime.availableProcessors)
}
for( (task, taskToNode) <- p.act(in, s)) yield
processResult(runTask(task, checkCycles, maxThreads)(taskToNode), s, s.fail)
}
}
}
def discover = Command { case s @ State(analysis: inc.Analysis) =>
Apply.simple(Discover, DiscoverBrief, DiscoverDetailed) { (in, s) =>
def discover = Command.simple(Discover, DiscoverBrief, DiscoverDetailed) { (in, s) =>
withAttribute(s, Analysis, "No analysis to process.") { analysis =>
val command = Parse.discover(in.arguments)
val discovered = Build.discover(analysis, command)
println(discovered.mkString("\n"))
s
}(s)
}
}
def compile = Command.simple(CompileName, CompileBrief, CompileDetailed ) { (in, s) =>
val command = Parse.compile(in.arguments)(s.baseDir)
try {
val analysis = Build.compile(command, s.configuration)
s.copy(project = analysis)()
s.put(Analysis, analysis)
} catch { case e: xsbti.CompileFailed => s.fail /* already logged */ }
}
def load = Command.simple(Load, Parse.helpBrief(Load, LoadLabel), Parse.helpDetail(Load, LoadLabel, false) ) { (in, s) =>
loadCommand(in.arguments, s.configuration, false, "sbt.Project") match
{
case Right(Seq(newValue)) => runExitHooks(s).copy(project = newValue)()
case Left(e) => handleException(e, s, false)
}
}
def loadProject = Command.simple(LoadProject, LoadProjectBrief, LoadProjectDetailed) { (in, s) =>
val base = s.configuration.baseDirectory
lazy val p: Project = MultiProject.load(s.configuration, ConsoleLogger() /*TODO*/, ProjectInfo.externals(exts))(base)
lazy val p: Project = MultiProject.load(s.configuration, logger(s), ProjectInfo.externals(exts))(base)
// lazy so that p can forward-reference it
lazy val exts: Map[File, Project] = MultiProject.loadExternals(p :: Nil, p.info.construct).updated(base, p)
exts// force
runExitHooks(s).copy(project = p)().put(MultiProject.InitialProject, p)
setProject(p, p, runExitHooks(s))
}
def setProject(p: Project, initial: Project, s: State): State =
{
logger(s).info("Set current project to " + p.name)
val nav = new MultiNavigation(p, setProject _, p, initial)
val watched = new MultiWatched(p)
// put(Logged, p.log)
val newAttrs = s.attributes.put(Analysis, p.info.analysis).put(Navigate, nav).put(Watch, watched).put(HistoryPath, p.historyPath).put(TaskedKey, p)
s.copy(attributes = newAttrs)
}
def handleException(e: Throwable, s: State, trace: Boolean = true): State = {
// TODO: log instead of print
if(trace)
e.printStackTrace
System.err.println(e.toString)
val log = logger(s)
if(trace) log.trace(e)
log.error(e.toString)
s.fail
}
def runExitHooks(s: State): State = {
ExitHooks.runExitHooks(s.exitHooks.toSeq)
s.copy()(exitHooks = Set.empty)
s.copy(exitHooks = Set.empty)
}
def loadCommands = Command.simple(LoadCommand, Parse.helpBrief(LoadCommand, LoadCommandLabel), Parse.helpDetail(LoadCommand, LoadCommandLabel, true) ) { (in, s) =>
@ -320,7 +309,7 @@ object Commands
case c: CommandDefinitions => c.commands
case x => error("Not an instance of CommandDefinitions: " + x.asInstanceOf[AnyRef].getClass)
}
s.copy()(processors = asCommands ++ s.processors)
s.copy(processors = asCommands ++ s.processors)
case Left(e) => handleException(e, s, false)
}
@ -356,31 +345,19 @@ object Commands
onFailure
}
def completor(s: State): jline.Completor = new jline.Completor {
lazy val apply = applicable(s)
def complete(buffer: String, cursor: Int, candidates: java.util.List[_]): Int =
{
val correct = candidates.asInstanceOf[java.util.List[String]]
val in = Input(buffer, Some(cursor))
val completions = apply.map(_.complete(in))
val maxPos = if(completions.isEmpty) -1 else completions.map(_.position).max
correct ++= ( completions flatMap { c => if(c.position == maxPos) c.candidates else Nil } )
maxPos
}
}
def addAlias(s: State, name: String, value: String): State =
{
val in = Input(name, None)
if(in.name == name) {
val removed = removeAlias(s, name)
if(value.isEmpty) removed else removed.copy()(processors = new Alias(name, value) +: removed.processors)
if(value.isEmpty) removed else removed.copy(processors = new Alias(name, value) +: removed.processors)
} else {
System.err.println("Invalid alias name '" + name + "'.")
s.fail
}
}
def removeAlias(s: State, name: String): State =
s.copy()(processors = s.processors.filter { case a: Alias if a.name == name => false; case _ => true } )
s.copy(processors = s.processors.filter { case a: Alias if a.name == name => false; case _ => true } )
def printAliases(s: State): Unit = {
val strings = aliasStrings(s)
@ -396,8 +373,7 @@ object Commands
final class Alias(val name: String, val value: String) extends Command {
assert(name.length > 0)
assert(value.length > 0)
def applies = s => Some(Apply() {
case in if in.name == name=> (value + " " + in.arguments) :: s
})
def help = _ => Nil
def run = (in, s) => if(in.name == name) Some((value + " " + in.arguments) :: s) else None
}
}

View File

@ -18,7 +18,6 @@ import annotation.tailrec
object MultiProject
{
val InitialProject = AttributeKey[Project]("initial-project")
val ScalaVersion = AttributeKey[String]("scala-version")
val defaultExcludes: FileFilter = (".*" - ".") || HiddenFileFilter
@ -103,7 +102,6 @@ object MultiProject
Dag.topologicalSort(root) { p =>
(externalProjects(p) map resolveExternal) ++ internalProjects(p)
}
def initialProject(state: State, ifUnset: => Project): Project =state get MultiProject.InitialProject getOrElse ifUnset
def makeContext(root: Project) =
{
@ -155,13 +153,30 @@ object MultiContext
val static = (o: Owner, s: String) => context(o).static(o, s)
}
}
final class MultiNavigation(val self: Project, val parentProject: Option[Project]) extends Navigation[Project]
final class MultiNavigation(val self: Project, val selectFun: (Project, Project, State) => State, val selectedProject: Project, val initialProject: Project) extends Navigation
{
def initialProject(s: State): Project = MultiProject.initialProject(s, rootProject)
def projectClosure(s: State): Seq[Project] = MultiProject.topologicalSort(initialProject(s))
@tailrec final lazy val rootProject: Project = parentProject match { case Some(p) => p.navigation.rootProject; case None => self }
type Project = sbt.Project
def parent = self.info.parent map nav
def name = self.name
def select(s: State): State = if(selectedProject == self) s else selectFun(self, initialProject, s)
@tailrec final lazy val root: MultiNavigation = parent match { case Some(p) => p.root; case None => this }
def initial = nav(initialProject)
def closure = projectClosure map nav
def selected = nav(selectedProject)
def projectClosure: Seq[Project] = MultiProject.topologicalSort(initialProject)
private val nav = (p: Project) => new MultiNavigation(p, selectFun, selectedProject, initialProject)
}
trait Project extends Tasked with HistoryEnabled with Member[Project] with Named with ConsoleTask with Watched
final class MultiWatched(val self: Project) extends Watched
{
def watched(p: Project): Seq[Watched] = MultiProject.topologicalSort(p)
def sourcePaths(p: Project): PathFinder = (Path.emptyPathFinder /: watched(p))(_ +++ _.watchPaths)
override def watchPaths = sourcePaths(self)
override def terminateWatch(key: Int): Boolean = self.terminateWatch(key)
}
trait Project extends Tasked with ConsoleTask with Watched
{
val info: ProjectInfo
@ -170,11 +185,9 @@ trait Project extends Tasked with HistoryEnabled with Member[Project] with Named
def base = info.projectDirectory
def outputRootPath = base / "target"
def historyPath = Some(outputRootPath / ".history")
def historyPath: Option[File] = Some(outputRootPath / ".history")
def streamBase = outputRootPath / "streams"
def navigation: Navigation[Project] = new MultiNavigation(this, info.parent)
implicit def streams = Dummy.Streams
def input = Dummy.In
def state = Dummy.State

View File

@ -6,15 +6,21 @@ package sbt
import java.io.File
import CommandSupport.FailureWall
case class State(project: Any)(
val configuration: xsbti.AppConfiguration,
val processors: Seq[Command],
val exitHooks: Set[ExitHook],
val onFailure: Option[String],
val commands: Seq[String],
val attributes: AttributeMap,
val next: Next.Value
)
final case class State(
configuration: xsbti.AppConfiguration,
processors: Seq[Command],
exitHooks: Set[ExitHook],
onFailure: Option[String],
commands: Seq[String],
attributes: AttributeMap,
next: Next.Value
) extends Identity
trait Identity {
override final def hashCode = super.hashCode
override final def equals(a: Any) = super.equals(a)
override final def toString = super.toString
}
trait StateOps {
def process(f: (String, State) => State): State
@ -35,21 +41,21 @@ object State
implicit def stateOps(s: State): StateOps = new StateOps {
def process(f: (String, State) => State): State =
s.commands match {
case Seq(x, xs @ _*) => f(x, s.copy()(commands = xs))
case Seq(x, xs @ _*) => f(x, s.copy(commands = xs))
case Seq() => exit(true)
}
s.copy()(commands = s.commands.drop(1))
def ::: (newCommands: Seq[String]): State = s.copy()(commands = newCommands ++ s.commands)
s.copy(commands = s.commands.drop(1))
def ::: (newCommands: Seq[String]): State = s.copy(commands = newCommands ++ s.commands)
def :: (command: String): State = (command :: Nil) ::: this
def ++ (newCommands: Seq[Command]): State = s.copy()(processors = s.processors ++ newCommands)
def ++ (newCommands: Seq[Command]): State = s.copy(processors = s.processors ++ newCommands)
def + (newCommand: Command): State = this ++ (newCommand :: Nil)
def baseDir: File = s.configuration.baseDirectory
def setNext(n: Next.Value) = s.copy()(next = n)
def setNext(n: Next.Value) = s.copy(next = n)
def continue = setNext(Next.Continue)
def reload = setNext(Next.Reload)
def exit(ok: Boolean) = setNext(if(ok) Next.Fail else Next.Done)
def get[T](key: AttributeKey[T]) = s.attributes.get(key)
def put[T](key: AttributeKey[T], value: T) = s.copy()(attributes = s.attributes.put(key, value))
def put[T](key: AttributeKey[T], value: T) = s.copy(attributes = s.attributes.put(key, value))
def fail =
{
val remaining = s.commands.dropWhile(_ != FailureWall)
@ -57,12 +63,12 @@ object State
{
s.onFailure match
{
case Some(c) => s.copy()(commands = c :: Nil, onFailure = None)
case Some(c) => s.copy(commands = c :: Nil, onFailure = None)
case None => exit(ok = false)
}
}
else
s.copy()(commands = remaining)
s.copy(commands = remaining)
}
}
}

View File

@ -2,4 +2,4 @@ package sbt
import std._
trait TestProject extends Project with ReflectiveProject with ProjectConstructors with LastOutput with PrintTask with ProjectExtra with TaskSetup with Exec with Javap
trait TestProject extends Project with ReflectiveProject with ProjectConstructors with LastOutput with PrintTask with ProjectExtra with Exec with Javap

View File

@ -18,13 +18,13 @@ object Watched
{
val PollDelaySeconds = 1
def isEnter(key: Int): Boolean = key == 10 || key == 13
/*
def watched(p: Project): Seq[Watched] = MultiProject.topologicalSort(p).collect { case w: Watched => w }
def sourcePaths(p: Project): PathFinder = (Path.emptyPathFinder /: watched(p))(_ +++ _.watchPaths)
def executeContinuously(project: Project with Watched, s: State, in: Input): State =
def sourcePaths(p: Project): PathFinder = (Path.emptyPathFinder /: watched(p))(_ +++ _.watchPaths)*/
def executeContinuously(watched: Watched, s: State, in: Input): State =
{
@tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (project.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = sourcePaths(project)
@tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate)
val sourcesFinder = watched.watchPaths
val watchState = s get ContinuousState getOrElse WatchState.empty
if(watchState.count > 0)

View File

@ -4,6 +4,8 @@
package sbt
package complete
import java.io.File
object HistoryCommands
{
val Start = "!"
@ -38,7 +40,7 @@ object HistoryCommands
def printHelp(): Unit =
println(helpString)
def apply(s: String, historyPath: Option[Path], maxLines: Int, error: String => Unit): Option[List[String]] =
def apply(s: String, historyPath: Option[File], maxLines: Int, error: String => Unit): Option[List[String]] =
if(s.isEmpty)
{
printHelp()
@ -46,7 +48,7 @@ object HistoryCommands
}
else
{
val lines = historyPath.toList.flatMap(h => IO.readLines(h.asFile) ).toArray
val lines = historyPath.toList.flatMap( p => IO.readLines(p) ).toArray
if(lines.isEmpty)
{
error("No history")
@ -66,7 +68,7 @@ object HistoryCommands
{
val command = historyCommand(history, s)
command.foreach(lines(lines.length - 1) = _)
historyPath foreach { h => IO.writeLines(h.asFile, lines) }
historyPath foreach { h => IO.writeLines(h, lines) }
Some(command.toList)
}
}

View File

@ -2,7 +2,10 @@
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
import jline.{Completor, ConsoleReader}
import jline.{Completor, ConsoleReader}
import java.io.File
abstract class JLine extends LineReader
{
protected[this] val reader: ConsoleReader
@ -37,10 +40,10 @@ private object JLine
try { action }
finally { t.enableEcho() }
}
private[sbt] def initializeHistory(cr: ConsoleReader, historyPath: Option[Path]): Unit =
private[sbt] def initializeHistory(cr: ConsoleReader, historyPath: Option[File]): Unit =
for(historyLocation <- historyPath)
{
val historyFile = historyLocation.asFile
val historyFile = historyLocation.getAbsoluteFile
ErrorHandling.wideConvert
{
historyFile.getParentFile.mkdirs()
@ -49,7 +52,7 @@ private object JLine
history.setHistoryFile(historyFile)
}
}
def simple(historyPath: Option[Path]): SimpleReader = new SimpleReader(historyPath)
def simple(historyPath: Option[File]): SimpleReader = new SimpleReader(historyPath)
val MaxHistorySize = 500
}
@ -57,14 +60,14 @@ trait LineReader extends NotNull
{
def readLine(prompt: String): Option[String]
}
private[sbt] final class LazyJLineReader(historyPath: Option[Path], completor: => Completor) extends JLine
private[sbt] final class LazyJLineReader(historyPath: Option[File] /*, completor: => Completor*/) extends JLine
{
protected[this] val reader =
{
val cr = new ConsoleReader
cr.setBellEnabled(false)
JLine.initializeHistory(cr, historyPath)
cr.addCompletor(new LazyCompletor(completor))
// cr.addCompletor(new LazyCompletor(completor))
cr
}
}
@ -75,7 +78,7 @@ private class LazyCompletor(delegate0: => Completor) extends Completor
delegate.complete(buffer, cursor, candidates)
}
class SimpleReader private[sbt] (historyPath: Option[Path]) extends JLine
class SimpleReader private[sbt] (historyPath: Option[File]) extends JLine
{
protected[this] val reader = JLine.createReader()
JLine.initializeHistory(reader, historyPath)