From 6960d2415862851444648ff83ce4584237e2df50 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sun, 19 Dec 2010 12:03:10 -0500 Subject: [PATCH] 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 --- main/Command.scala | 110 ++++++---------- main/CommandSupport.scala | 11 +- main/Main.scala | 194 ++++++++++++---------------- main/MultiProject.scala | 33 +++-- main/State.scala | 40 +++--- main/TestProject.scala | 2 +- main/Watched.scala | 10 +- util/complete/HistoryCommands.scala | 8 +- util/complete/LineReader.scala | 17 ++- 9 files changed, 197 insertions(+), 228 deletions(-) diff --git a/main/Command.scala b/main/Command.scala index 06129e9c2..2feba2801 100644 --- a/main/Command.scala +++ b/main/Command.scala @@ -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 } diff --git a/main/CommandSupport.scala b/main/CommandSupport.scala index 4d38ff780..dc1e93bb0 100644 --- a/main/CommandSupport.scala +++ b/main/CommandSupport.scala @@ -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) :: diff --git a/main/Main.scala b/main/Main.scala index df857bfb1..8922d7a30 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -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 } } \ No newline at end of file diff --git a/main/MultiProject.scala b/main/MultiProject.scala index fd68a9b97..479c3e408 100644 --- a/main/MultiProject.scala +++ b/main/MultiProject.scala @@ -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 diff --git a/main/State.scala b/main/State.scala index 7360aa2b0..6da718698 100644 --- a/main/State.scala +++ b/main/State.scala @@ -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) } } } \ No newline at end of file diff --git a/main/TestProject.scala b/main/TestProject.scala index 318ebb9b2..dd039e0de 100644 --- a/main/TestProject.scala +++ b/main/TestProject.scala @@ -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 \ No newline at end of file +trait TestProject extends Project with ReflectiveProject with ProjectConstructors with LastOutput with PrintTask with ProjectExtra with Exec with Javap \ No newline at end of file diff --git a/main/Watched.scala b/main/Watched.scala index 437948848..bb8964c2e 100644 --- a/main/Watched.scala +++ b/main/Watched.scala @@ -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) diff --git a/util/complete/HistoryCommands.scala b/util/complete/HistoryCommands.scala index a5b321d8c..bbf8c0bfd 100644 --- a/util/complete/HistoryCommands.scala +++ b/util/complete/HistoryCommands.scala @@ -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) } } diff --git a/util/complete/LineReader.scala b/util/complete/LineReader.scala index e7c9ec67a..d586d0729 100644 --- a/util/complete/LineReader.scala +++ b/util/complete/LineReader.scala @@ -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)