mirror of https://github.com/sbt/sbt.git
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:
parent
20a5e3b3c7
commit
6960d24158
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) ::
|
||||
|
|
|
|||
194
main/Main.scala
194
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue