mirror of https://github.com/sbt/sbt.git
adding more commands
This commit is contained in:
parent
8e255bc226
commit
767a1e47c1
|
|
@ -44,11 +44,11 @@ object SameAPI
|
|||
{
|
||||
val start = System.currentTimeMillis
|
||||
|
||||
println("\n=========== API #1 ================")
|
||||
/*println("\n=========== API #1 ================")
|
||||
import DefaultShowAPI._
|
||||
println(ShowAPI.show(a))
|
||||
println("\n=========== API #2 ================")
|
||||
println(ShowAPI.show(b))
|
||||
println(ShowAPI.show(b))*/
|
||||
|
||||
/** de Bruijn levels for type parameters in source a and b*/
|
||||
val tagsA = TagTypeVariables(a)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ package sbt
|
|||
package inc
|
||||
|
||||
final case class Discovered(baseClasses: Set[String], annotations: Set[String], hasMain: Boolean, isModule: Boolean)
|
||||
{
|
||||
def isEmpty = baseClasses.isEmpty && annotations.isEmpty
|
||||
}
|
||||
object Discovered
|
||||
{
|
||||
def empty = new Discovered(Set.empty, Set.empty, false, false)
|
||||
|
|
|
|||
|
|
@ -4,33 +4,68 @@
|
|||
package sbt
|
||||
|
||||
import Execute.NodeView
|
||||
import Completions.noCompletions
|
||||
|
||||
sealed trait Command
|
||||
{
|
||||
def applies: State => Option[Apply]
|
||||
}
|
||||
trait UserCommand extends Command
|
||||
trait Apply
|
||||
{
|
||||
def help: Seq[(String,String)]
|
||||
def help: Seq[Help]
|
||||
def run: Input => Option[State]
|
||||
def complete: Input => Completions
|
||||
}
|
||||
trait Help
|
||||
{
|
||||
def detail: (Set[String], String)
|
||||
def brief: (String, String)
|
||||
}
|
||||
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() }
|
||||
}
|
||||
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)
|
||||
|
||||
def simple(name: String, help: (String, String)*)(f: State => State): Command =
|
||||
apply { case s => Apply(help) { case in if in.line == name => f(s) }}
|
||||
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: Seq[(String,String)])(r: Input => Option[State]): Apply =
|
||||
new Apply { def help = h; def run = r }
|
||||
|
||||
def apply(h: Seq[(String,String)])(r: PartialFunction[Input, State]): Apply =
|
||||
direct(h)(r.lift)
|
||||
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) )
|
||||
s => Apply( h ){ case in if name == in.name => f( in, s) }
|
||||
}
|
||||
}
|
||||
|
||||
trait Logged
|
||||
|
|
@ -41,22 +76,34 @@ trait HistoryEnabled
|
|||
{
|
||||
def historyPath: Option[Path]
|
||||
}
|
||||
trait Named
|
||||
{
|
||||
def name: String
|
||||
}
|
||||
trait Member[Node <: Member[Node]]
|
||||
{ self: Node =>
|
||||
def projectClosure: Seq[Node]
|
||||
}
|
||||
trait Tasked
|
||||
{
|
||||
type Task[T] <: AnyRef
|
||||
def task(name: String, state: State): Option[Task[State]]
|
||||
implicit def taskToNode: NodeView[Task]
|
||||
def help: Seq[(String, String)]
|
||||
def help: Seq[Help]
|
||||
}
|
||||
trait TaskSetup
|
||||
{
|
||||
def maxThreads = Runtime.getRuntime.availableProcessors
|
||||
def checkCycles = false
|
||||
}
|
||||
final case class Input(line: String)
|
||||
final case class Input(line: String, cursor: Option[Int])
|
||||
{
|
||||
def name: String = error("TODO")
|
||||
def arguments: String = error("TODO")
|
||||
lazy val (name, arguments) = line match { case Input.NameRegex(n, a) => (n, a); case _ => (line, "") }
|
||||
lazy val splitSpace = (arguments split """\s+""").toSeq
|
||||
}
|
||||
object Input
|
||||
{
|
||||
val NameRegex = """\s*(\p{Punct}+|[\w-]+)\s*(.*)""".r
|
||||
}
|
||||
|
||||
object Next extends Enumeration {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2010 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import complete.HistoryCommands
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import java.io.File
|
||||
import Path._
|
||||
|
||||
object CommandSupport
|
||||
{
|
||||
def logger(s: State) = s match {
|
||||
case State(p: Logged) => p.log
|
||||
case _ => new 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 sbtRCs(s: State): Seq[File] =
|
||||
(Path.userHome / sbtrc) ::
|
||||
(s.baseDir / sbtrc asFile) ::
|
||||
Nil
|
||||
|
||||
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine
|
||||
def processLine(s: String) = { val trimmed = s.trim; if(ignoreLine(trimmed)) None else Some(trimmed) }
|
||||
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#")
|
||||
|
||||
val HelpCommand = "help"
|
||||
val ProjectCommand = "project"
|
||||
val ProjectsCommand = "projects"
|
||||
|
||||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
|
||||
/** The list of command names that may be used to terminate the program.*/
|
||||
val TerminateActions: Seq[String] = Seq(Exit, Quit)
|
||||
|
||||
def helpBrief = (HelpCommand + " command*", "Displays this help message or prints detailed help on requested commands.")
|
||||
def helpDetailed = "If an argument is provided, this prints detailed help for that command.\nOtherwise, this prints a help summary."
|
||||
|
||||
def projectBrief = (ProjectCommand + " [project]", "Displays the current project or changes to the provided `project`.")
|
||||
def projectDetailed =
|
||||
ProjectCommand +
|
||||
"""
|
||||
Displays the name of the current project.
|
||||
|
||||
""" + ProjectCommand + """ name
|
||||
Changes to the project with the provided name.
|
||||
This command fails if there is no project with the given name.
|
||||
"""
|
||||
|
||||
def projectsBrief = (ProjectsCommand, projectsDetailed)
|
||||
def projectsDetailed = "Displays the names of available projects."
|
||||
|
||||
def historyHelp = HistoryCommands.descriptions.map( d => Help(d) )
|
||||
|
||||
def exitBrief = (TerminateActions.mkString(", "), "Terminates the build.")
|
||||
|
||||
def sbtrc = ".sbtrc"
|
||||
|
||||
def ReadCommand = "<"
|
||||
def ReadFiles = " file1 file2 ..."
|
||||
def ReadBrief = (ReadCommand + " file*", "Reads command lines from the provided files.")
|
||||
def ReadDetailed = ReadCommand + ReadFiles +
|
||||
"""
|
||||
Reads the lines from the given files and inserts them as commands.
|
||||
Any lines that are empty or that start with # are ignored.
|
||||
If a file does not exist or is not readable, this command fails.
|
||||
|
||||
All commands are read before any are executed.
|
||||
Therefore, if any file is not readable, no commands from any files will be
|
||||
run.
|
||||
|
||||
You probably need to escape this command if entering it at your shell.
|
||||
"""
|
||||
|
||||
def DefaultsCommand = "add-default-commands"
|
||||
def DefaultsBrief = (DefaultsCommand, DefaultsDetailed)
|
||||
def DefaultsDetailed = "Registers default built-in commands"
|
||||
|
||||
def ReloadCommand = "reload"
|
||||
def ReloadBrief = (ReloadCommand, "Reloads the session and continues to execute the remaining commands.")
|
||||
def ReloadDetailed =
|
||||
ReloadCommand + """
|
||||
This command is equivalent to exiting, restarting, and running the
|
||||
remaining commands with the exception that the jvm is not shut down.
|
||||
"""
|
||||
|
||||
def Multi = ";"
|
||||
def MultiBrief = ("( " + Multi + " command )+", MultiDetailed)
|
||||
def MultiDetailed = "Runs the provided semicolon-separated commands."
|
||||
|
||||
val AliasCommand = "alias"
|
||||
def AliasBrief = (AliasCommand, "Adds, removes, or prints command aliases.")
|
||||
def AliasDetailed =
|
||||
AliasCommand + """
|
||||
Prints a list of defined aliases.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name
|
||||
Prints the alias defined for `name`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=value
|
||||
Sets the alias `name` to `value`, replacing any existing alias with that name.
|
||||
Whenever `name` is entered, value is run.
|
||||
If any arguments are provided to `name`, those are appended to `value`.
|
||||
|
||||
""" +
|
||||
AliasCommand + """ name=
|
||||
Removes the alias for `name`.
|
||||
"""
|
||||
|
||||
def Load = "load"
|
||||
def LoadLabel = "a project"
|
||||
def LoadCommand = "load-commands"
|
||||
def LoadCommandLabel = "commands"
|
||||
|
||||
def Shell = "shell"
|
||||
def ShellBrief = (Shell, ShellDetailed)
|
||||
def ShellDetailed = "Provides an interactive prompt from which commands can be run."
|
||||
|
||||
def OnFailure = "-"
|
||||
def OnFailureBrief = (OnFailure + " command", "Registers 'command' to run if a command fails.")
|
||||
def OnFailureDetailed =
|
||||
OnFailure + """ command
|
||||
Registers 'command' to run when a command fails to complete normally.
|
||||
Only one failure command may be registered at a time, so this
|
||||
command replaces the previous command if there is one.
|
||||
The failure command is reset when it runs, so it must be added again
|
||||
if desired.
|
||||
"""
|
||||
|
||||
def IfLast = "iflast"
|
||||
def IfLastBrief = (IfLast + " command", IfLastDetailed)
|
||||
def IfLastDetailed = "If there are no more commands after this one, 'command' is run."
|
||||
|
||||
def InitCommand = "initialize"
|
||||
def InitBrief = (InitCommand, "Initializes command processing.")
|
||||
def InitDetailed =
|
||||
InitCommand + """
|
||||
Initializes command processing.
|
||||
|
||||
Runs the following commands.
|
||||
|
||||
defaults
|
||||
Registers default commands.
|
||||
|
||||
load-commands -base ~/.sbt/commands
|
||||
Builds and loads command definitions from ~/.sbt/commands
|
||||
|
||||
< ~/.sbtrc
|
||||
< .sbtrc
|
||||
Runs commands from ~/.sbtrc and ./.sbtrc if they exist
|
||||
"""
|
||||
}
|
||||
229
main/Main.scala
229
main/Main.scala
|
|
@ -6,17 +6,23 @@ package sbt
|
|||
import Execute.NodeView
|
||||
import complete.HistoryCommands
|
||||
import HistoryCommands.{Start => HistoryPrefix}
|
||||
import sbt.build.{AggressiveCompile, Build, BuildException, Parse, ParseException}
|
||||
import sbt.build.{AggressiveCompile, Auto, Build, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad}
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConversions._
|
||||
import Path._
|
||||
|
||||
import java.io.File
|
||||
|
||||
/** This class is the entry point for sbt.*/
|
||||
class xMain extends xsbti.AppMain
|
||||
{
|
||||
final def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
|
||||
{
|
||||
import Commands._
|
||||
val initialCommands = Seq(help, history, exit, load)
|
||||
val state = State( () )( configuration, initialCommands, Set.empty, None, configuration.arguments.map(_.trim).toList, Next.Continue )
|
||||
import Commands.{initialize, defaults}
|
||||
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, Next.Continue )
|
||||
run(state)
|
||||
}
|
||||
|
||||
|
|
@ -36,53 +42,144 @@ class xMain extends xsbti.AppMain
|
|||
def next(state: State): State = state.process(process)
|
||||
def process(command: String, state: State): State =
|
||||
{
|
||||
val in = Input(command)
|
||||
val in = Input(command, None)
|
||||
Commands.applicable(state).flatMap( _.run(in) ).headOption.getOrElse {
|
||||
System.err.println("Unknown command '" + command + "'")
|
||||
state.fail
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
import CommandSupport._
|
||||
object Commands
|
||||
{
|
||||
def DefaultCommands = Seq(help, reload, read, history, exit, load, loadCommands, projects, project, setOnFailure, ifLast, multi, shell, alias, act)
|
||||
|
||||
def applicable(state: State): Stream[Apply] =
|
||||
state.processors.toStream.flatMap(_.applies(state) )
|
||||
|
||||
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 argStr = (in.line stripPrefix HelpCommand).trim
|
||||
|
||||
def help = Command.simple("help", ("help", "Displays this help message.")) { s =>
|
||||
val message = applicable(s).flatMap(_.help).map { case (a,b) => a + " : " + b }.mkString("\n")
|
||||
val message =
|
||||
if(argStr.isEmpty)
|
||||
h.map( _.brief match { case (a,b) => a + " : " + b } ).mkString("\n", "\n", "\n")
|
||||
else
|
||||
h flatMap detail( argStr.split("""\s+""", 0) ) mkString("\n", "\n\n", "\n")
|
||||
System.out.println(message)
|
||||
s
|
||||
}
|
||||
|
||||
def alias = Command.simple(AliasCommand, AliasBrief, AliasDetailed) { (in, s) =>
|
||||
in.arguments.split("""\s*=\s*""",2).toSeq match {
|
||||
case Seq(name, value) => addAlias(s, name.trim, value.trim)
|
||||
case Seq(x) if !x.isEmpty=> printAlias(s, x.trim); s
|
||||
case _ => printAliases(s); s
|
||||
}
|
||||
}
|
||||
|
||||
def shell = Command.simple(Shell, ShellBrief, ShellDetailed) { (in, s) =>
|
||||
val historyPath = s.project match { case he: HistoryEnabled => he.historyPath; case _ => None }
|
||||
val reader = new LazyJLineReader(historyPath, new LazyCompletor(completor(s)))
|
||||
val line = reader.readLine("> ")
|
||||
line match { case Some(line) => line :: Shell :: s; case None => s }
|
||||
}
|
||||
|
||||
def multi = Command.simple(Multi, MultiBrief, MultiDetailed) { (in, s) =>
|
||||
in.arguments.split(";").toSeq ::: s
|
||||
}
|
||||
|
||||
def ifLast = Command.simple(IfLast, IfLastBrief, IfLastDetailed) { (in, s) =>
|
||||
if(s.commands.isEmpty) in.arguments :: s else s
|
||||
}
|
||||
|
||||
def setOnFailure = Command.simple(OnFailure, OnFailureBrief, OnFailureDetailed) { (in, s) =>
|
||||
s.copy()(onFailure = Some(in.arguments))
|
||||
}
|
||||
|
||||
def reload = Command.simple(ReloadCommand, ReloadBrief, ReloadDetailed) { (in, s) =>
|
||||
runExitHooks(s).reload
|
||||
}
|
||||
|
||||
def defaults = Command.simple(DefaultsCommand, DefaultsBrief, DefaultsDetailed) { (in, s) =>
|
||||
s ++ DefaultCommands
|
||||
}
|
||||
|
||||
def initialize = Command.simple(InitCommand, InitBrief, InitDetailed) { (in, s) =>
|
||||
readLines( readable( sbtRCs(s) ) ) ::: s
|
||||
}
|
||||
|
||||
def read = Command.simple(ReadCommand, ReadBrief, ReadDetailed) { (in, s) =>
|
||||
val from = in.splitSpace map { p => new File(s.baseDir, p) }
|
||||
val notFound = notReadable(from)
|
||||
if(notFound.isEmpty)
|
||||
readLines(from) ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed
|
||||
else {
|
||||
logger(s).error("File(s) not readable: \n\t" + notFound.mkString("\n\t"))
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
def history = Command { case s @ State(p: HistoryEnabled with Logged) =>
|
||||
Apply(HistoryCommands.descriptions) {
|
||||
Apply( historyHelp: _* ) {
|
||||
case in if in.line startsWith("!") =>
|
||||
HistoryCommands(in.line.substring(HistoryPrefix.length).trim, p.historyPath, 500/*JLine.MaxHistorySize*/, p.log.error _) match
|
||||
{
|
||||
case Some(commands) =>
|
||||
commands.foreach(println) //better to print it than to log it
|
||||
commands.foreach(println) //printing is more appropriate than logging
|
||||
(commands ::: s).continue
|
||||
case None => s.fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def listProject(p: Named, log: Logger) = printProject("\t", p, log)
|
||||
def printProject(prefix: String, p: Named, log: Logger) = log.info(prefix + p.name)
|
||||
|
||||
def projects = Command { case s @ State(d: Member[_]) =>
|
||||
Apply.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) =>
|
||||
val log = logger(s)
|
||||
d.projectClosure.foreach { case n: Named => listProject(n, log) }
|
||||
s
|
||||
}(s)
|
||||
}
|
||||
|
||||
def project = Command { case s @ State(d: Member[_] with Named) =>
|
||||
Apply.simple(ProjectCommand, projectBrief, projectDetailed ) { (in,s) =>
|
||||
val to = in.arguments
|
||||
if(to.isEmpty)
|
||||
{
|
||||
logger(s).info(d.name)
|
||||
s
|
||||
}
|
||||
else
|
||||
{
|
||||
d.projectClosure.find { case n: Named => n.name == to; case _ => false } match
|
||||
{
|
||||
case Some(np) => logger(s).info("Set current project to " + to); s.copy(np)()
|
||||
case None => logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects)."); s.fail
|
||||
}
|
||||
}
|
||||
}(s)
|
||||
}
|
||||
|
||||
def helpExit = (TerminateActions.mkString(", "), "Terminates the build.")
|
||||
|
||||
def exit = Command { case s => Apply(helpExit :: Nil) {
|
||||
case Input(line) if TerminateActions contains line =>
|
||||
s.exit(true)
|
||||
def exit = Command { case s => Apply( Help(exitBrief) ) {
|
||||
case in if TerminateActions contains in.line =>
|
||||
runExitHooks(s).exit(true)
|
||||
}
|
||||
}
|
||||
|
||||
def act = Command { case s @ State(p: Tasked) =>
|
||||
new Apply {
|
||||
def help = p.help
|
||||
def complete = in => Completions()
|
||||
def run = in => {
|
||||
lazy val (checkCycles, maxThreads) = p match {
|
||||
val (checkCycles, maxThreads) = p match {
|
||||
case c: TaskSetup => (c.checkCycles, c.maxThreads)
|
||||
case _ => (false, Runtime.getRuntime.availableProcessors)
|
||||
}
|
||||
|
|
@ -91,28 +188,44 @@ object Commands
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
def load = Command { case s => Apply(Nil) {
|
||||
case Input(line) if line.startsWith("load") =>
|
||||
loadCommand(line, s.configuration) match
|
||||
{
|
||||
case Right(newValue) =>
|
||||
ExitHooks.runExitHooks(s.exitHooks.toSeq)
|
||||
s.copy(project = newValue)(exitHooks = Set.empty)
|
||||
case Left(e) => e.printStackTrace; System.err.println(e.toString); s.fail // TODO: log instead of print
|
||||
}
|
||||
|
||||
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 // TODO: classOf[Project].getName when ready
|
||||
{
|
||||
case Right(Seq(newValue)) => runExitHooks(s).copy(project = newValue)()
|
||||
case Left(e) => e.printStackTrace; System.err.println(e.toString); s.fail // TODO: log instead of print
|
||||
}
|
||||
}
|
||||
|
||||
def loadCommand(line: String, configuration: xsbti.AppConfiguration): Either[Throwable, Any] =
|
||||
try { Right( Build( Parse(line)(configuration.baseDirectory), configuration ) ) }
|
||||
catch { case e @ (_: ParseException | _: BuildException | _: xsbti.CompileFailed) => Left(e) }
|
||||
|
||||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
/** The list of command names that may be used to terminate the program.*/
|
||||
val TerminateActions: Seq[String] = Seq(Exit, Quit)
|
||||
def runExitHooks(s: State): State = {
|
||||
ExitHooks.runExitHooks(s.exitHooks.toSeq)
|
||||
s.copy()(exitHooks = Set.empty)
|
||||
}
|
||||
|
||||
def loadCommands = Command.simple(LoadCommand, Parse.helpBrief(LoadCommand, LoadCommandLabel), Parse.helpDetail(LoadCommand, LoadCommandLabel, true) ) { (in, s) =>
|
||||
loadCommand(in.arguments, s.configuration, true, classOf[UserCommand].getName) match
|
||||
{
|
||||
case Right(newCommands) =>
|
||||
val asCommands = newCommands map { case c: Command => c; case x => error("Not a Command: " + x.asInstanceOf[AnyRef].getClass) }
|
||||
s.copy()(processors = asCommands ++ s.processors)
|
||||
case Left(e) => e.printStackTrace; System.err.println(e.toString); s.fail // TODO: log instead of print
|
||||
}
|
||||
}
|
||||
|
||||
def loadCommand(line: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean, defaultSuper: String): Either[Throwable, Seq[Any]] =
|
||||
try
|
||||
{
|
||||
val parsed = Parse(line)(configuration.baseDirectory)
|
||||
Right( Build( translateEmpty(parsed, defaultSuper), configuration, allowMultiple) )
|
||||
}
|
||||
catch { case e @ (_: ParseException | _: BuildException | _: xsbti.CompileFailed) => Left(e) }
|
||||
|
||||
def translateEmpty(load: LoadCommand, defaultSuper: String): LoadCommand =
|
||||
load match {
|
||||
case ProjectLoad(base, Auto.Explicit, "") => ProjectLoad(base, Auto.Subclass, defaultSuper)
|
||||
case s @ SourceLoad(_, _, _, _, Auto.Explicit, "") => s.copy(auto = Auto.Subclass, name = defaultSuper)
|
||||
case x => x
|
||||
}
|
||||
|
||||
def runTask[Task[_] <: AnyRef](root: Task[State], checkCycles: Boolean, maxWorkers: Int)(implicit taskToNode: NodeView[Task]): Result[State] =
|
||||
{
|
||||
|
|
@ -127,6 +240,52 @@ object Commands
|
|||
case Value(v) => v
|
||||
case Inc(Incomplete(tpe, message, causes, directCause)) => // tpe: IValue = Error, message: Option[String] = None, causes: Seq[Incomplete] = Nil, directCause: Option[Throwable] = None)
|
||||
println("Task did not complete successfully (TODO: error logging)")
|
||||
directCause.foreach(_.printStackTrace)
|
||||
original
|
||||
}
|
||||
|
||||
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)
|
||||
} 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 } )
|
||||
|
||||
def printAliases(s: State): Unit = {
|
||||
val strings = aliasStrings(s)
|
||||
if(!strings.isEmpty) println( strings.mkString("\t", "\n\t","") )
|
||||
}
|
||||
|
||||
def printAlias(s: State, name: String): Unit =
|
||||
for(a <- aliases(s)) if (a.name == name) println("\t" + name + " = " + a.value)
|
||||
|
||||
def aliasStrings(s: State) = aliases(s).map(a => a.name + " = " + a.value)
|
||||
def aliases(s: State) = s.processors collect { case a: Alias => a }
|
||||
|
||||
final class Alias(val name: String, val value: String) extends UserCommand {
|
||||
assert(name.length > 0)
|
||||
assert(value.length > 0)
|
||||
def applies = s => Some(Apply() {
|
||||
case in if in.name == name=> (value + " " + in.arguments) :: s
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
case class State(project: Any)(
|
||||
val configuration: xsbti.AppConfiguration,
|
||||
val processors: Seq[Command],
|
||||
|
|
@ -20,18 +22,24 @@ trait StateOps {
|
|||
def reload: State
|
||||
def exit(ok: Boolean): State
|
||||
def fail: State
|
||||
def ++ (newCommands: Seq[Command]): State
|
||||
def + (newCommand: Command): State
|
||||
def baseDir: File
|
||||
}
|
||||
object State
|
||||
{
|
||||
implicit def stateOps(s: State): StateOps = new StateOps {
|
||||
def process(f: (String, State) => State): State =
|
||||
s.commands match {
|
||||
case x :: xs => f(x, s.copy()(commands = xs))
|
||||
case Nil => exit(true)
|
||||
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)
|
||||
def :: (command: String): State = s.copy()(commands = command +: s.commands)
|
||||
def :: (command: String): State = (command :: Nil) ::: this
|
||||
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 continue = setNext(Next.Continue)
|
||||
def reload = setNext(Next.Reload)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import java.io.File
|
|||
import classpath.ClasspathUtilities.toLoader
|
||||
import ModuleUtilities.getObject
|
||||
import compile.{AnalyzingCompiler, JavaCompiler}
|
||||
import inc.Analysis
|
||||
import Path._
|
||||
import GlobFilter._
|
||||
|
||||
|
|
@ -18,43 +19,36 @@ object Build
|
|||
def loader(configuration: xsbti.AppConfiguration): ClassLoader =
|
||||
configuration.provider.mainClass.getClassLoader
|
||||
|
||||
def apply(command: LoadCommand, configuration: xsbti.AppConfiguration): Any =
|
||||
def apply(command: LoadCommand, configuration: xsbti.AppConfiguration, allowMultiple: Boolean = false): Seq[Any] =
|
||||
command match
|
||||
{
|
||||
case BinaryLoad(classpath, module, name) =>
|
||||
binary(classpath, module, name, loader(configuration))
|
||||
binary(classpath, module, name, loader(configuration), allowMultiple)
|
||||
case SourceLoad(classpath, sourcepath, output, module, auto, name) =>
|
||||
source(classpath, sourcepath, output, module, auto, name, configuration)
|
||||
case ProjectLoad(base, name) =>
|
||||
project(base, name, configuration)
|
||||
source(classpath, sourcepath, output, module, auto, name, configuration, allowMultiple)._1
|
||||
case ProjectLoad(base, auto, name) =>
|
||||
project(base, auto, name, configuration, allowMultiple)._1
|
||||
}
|
||||
|
||||
def project(base: File, name: String, configuration: xsbti.AppConfiguration): Any =
|
||||
def project(base: File, auto: Auto.Value, name: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean = false): (Seq[Any], Analysis) =
|
||||
{
|
||||
val nonEmptyName = if(name.isEmpty) "Project" else name
|
||||
val buildDir = base / "project" / "build"
|
||||
val sources = buildDir * "*.scala" +++ buildDir / "src" / "main" / "scala" ** "*.scala"
|
||||
source(Nil, sources.get.toSeq, Some(buildDir / "target" asFile), false, Auto.Explicit, nonEmptyName, configuration)
|
||||
source(Nil, sources.get.toSeq, Some(buildDir / "target" asFile), false, auto, name, configuration, allowMultiple)
|
||||
}
|
||||
|
||||
def binary(classpath: Seq[File], module: Boolean, name: String, parent: ClassLoader): Any =
|
||||
def binary(classpath: Seq[File], module: Boolean, name: String, parent: ClassLoader, allowMultiple: Boolean = false): Seq[Any] =
|
||||
{
|
||||
if(name.isEmpty)
|
||||
error("Class name required to load binary project.")
|
||||
else
|
||||
{
|
||||
val loader = toLoader(classpath, parent)
|
||||
if(module)
|
||||
getObject(name, loader)
|
||||
else
|
||||
{
|
||||
val clazz = Class.forName(name, true, loader)
|
||||
clazz.newInstance
|
||||
}
|
||||
val names = if(allowMultiple) name.split(",").toSeq else Seq(name)
|
||||
binaries(classpath, module, names, parent)
|
||||
}
|
||||
}
|
||||
|
||||
def source(classpath: Seq[File], sources: Seq[File], output: Option[File], module: Boolean, auto: Auto.Value, name: String, configuration: xsbti.AppConfiguration): Any =
|
||||
def source(classpath: Seq[File], sources: Seq[File], output: Option[File], module: Boolean, auto: Auto.Value, name: String, configuration: xsbti.AppConfiguration, allowMultiple: Boolean = false): (Seq[Any], Analysis) =
|
||||
{
|
||||
// TODO: accept Logger as an argument
|
||||
val log = new ConsoleLogger with Logger with sbt.IvyLogger
|
||||
|
|
@ -78,7 +72,9 @@ object Build
|
|||
val analysis = agg(compiler, javac, sources, compileClasspath, outputDirectory, Nil, Nil)(log)
|
||||
|
||||
val discovered = discover(analysis, module, auto, name)
|
||||
load(discovered)(x => binary(projectClasspath, module, x, loader(configuration)) )
|
||||
val loaded = binaries(projectClasspath, module, check(discovered, allowMultiple), loader(configuration))
|
||||
|
||||
(loaded, analysis)
|
||||
}
|
||||
def discover(analysis: inc.Analysis, module: Boolean, auto: Auto.Value, name: String): Seq[String] =
|
||||
{
|
||||
|
|
@ -92,16 +88,33 @@ object Build
|
|||
def discover(analysis: inc.Analysis, module: Boolean, discovery: inc.Discovery): Seq[String] =
|
||||
{
|
||||
for(src <- analysis.apis.internal.values.toSeq;
|
||||
(df, found) <- discovery(src.definitions) if found.isModule == module)
|
||||
(df, found) <- discovery(src.definitions) if !found.isEmpty && found.isModule == module)
|
||||
yield
|
||||
df.name
|
||||
}
|
||||
def load(discovered: Seq[String])(doLoad: String => Any): Any =
|
||||
|
||||
def binaries(classpath: Seq[File], module: Boolean, names: Seq[String], parent: ClassLoader): Seq[Any] =
|
||||
loadBinaries(names, module, toLoader(classpath, parent))
|
||||
|
||||
def loadBinaries(names: Seq[String], module: Boolean, loader: ClassLoader): Seq[Any] =
|
||||
for(name <- names if !name.isEmpty) yield
|
||||
loadBinary(name, module, loader)
|
||||
|
||||
def loadBinary(name: String, module: Boolean, loader: ClassLoader): Any =
|
||||
if(module)
|
||||
getObject(name, loader)
|
||||
else
|
||||
{
|
||||
val clazz = Class.forName(name, true, loader)
|
||||
clazz.newInstance
|
||||
}
|
||||
|
||||
def check(discovered: Seq[String], allowMultiple: Boolean): Seq[String] =
|
||||
discovered match
|
||||
{
|
||||
case Seq() => error("No project found")
|
||||
case Seq(x) => doLoad(x)
|
||||
case xs => error("Multiple projects found: " + discovered.mkString(", "))
|
||||
case Seq(x) => discovered
|
||||
case _ => if(allowMultiple) discovered else error("Multiple projects found: " + discovered.mkString(", "))
|
||||
}
|
||||
|
||||
def error(msg: String) = throw new BuildException(msg)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import java.io.File
|
|||
sealed trait LoadCommand
|
||||
final case class BinaryLoad(classpath: Seq[File], module: Boolean, name: String) extends LoadCommand
|
||||
final case class SourceLoad(classpath: Seq[File], sourcepath: Seq[File], output: Option[File], module: Boolean, auto: Auto.Value, name: String) extends LoadCommand
|
||||
final case class ProjectLoad(base: File, name: String) extends LoadCommand
|
||||
final case class ProjectLoad(base: File, auto: Auto.Value, name: String) extends LoadCommand
|
||||
|
||||
object Auto extends Enumeration
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,48 +8,56 @@ import java.io.File
|
|||
|
||||
final class ParseException(msg: String) extends RuntimeException(msg)
|
||||
|
||||
/** Parses a load command.
|
||||
*
|
||||
* load ::= 'load' (binary | source | project)
|
||||
*
|
||||
* binary ::= classpath module name
|
||||
* source ::= classpath '-src' paths ('-d' dir)? ('-auto' ('sub' | 'annot'))? module name
|
||||
* project ::= ('-project' path)? name?
|
||||
*
|
||||
* name ::= '-name' nameString
|
||||
* module ::= ('-module' ('true'|'false') )?
|
||||
* classpath ::= '-cp' paths
|
||||
* path ::= pathChar+
|
||||
* paths ::= path (pathSep path)*
|
||||
*/
|
||||
|
||||
/** Parses a load command. The implementation is a quick hack.
|
||||
It is not robust and feedback is not helpful.*/
|
||||
object Parse
|
||||
{
|
||||
def helpBrief(name: String, label: String): (String, String) = (name + " <options>", "Loads " + label + " according to the specified options.")
|
||||
def helpDetail(name: String, label: String, multiple: Boolean) =
|
||||
"Loads " + label + """ by one of the following methods:
|
||||
1) binary: loads a class from an existing classpath
|
||||
2) source: compiles provided sources and loads a specific class
|
||||
3) project: builds from source using default settings
|
||||
|
||||
The command has the following syntax:
|
||||
|
||||
load ::= """ + name + """ ('-help' | binary | source | project)
|
||||
|
||||
binary ::= classpath module? name
|
||||
source ::= classpath? sources out? module? (detect | name)
|
||||
project ::= base? name?
|
||||
|
||||
base ::= '-project' path
|
||||
sources ::= '-src' paths
|
||||
out ::= '-d' dir
|
||||
detect ::= '-auto' ('sub' | 'annot')
|
||||
name ::= '-name' nameString
|
||||
module ::= '-module' ('true'|'false')
|
||||
classpath ::= '-cp' paths
|
||||
path ::= pathChar+
|
||||
paths ::= path (pathSep path)*
|
||||
""" +
|
||||
( if(multiple) "\nTo specify multiple names, separate them by commas." else "")
|
||||
|
||||
import File.{pathSeparatorChar => sep}
|
||||
|
||||
def error(msg: String) = throw new ParseException(msg)
|
||||
def apply(commandString: String)(implicit base: File): LoadCommand =
|
||||
{
|
||||
val tokens = commandString.split("""\s+""").toSeq
|
||||
if(tokens.isEmpty) error("Empty command")
|
||||
else if(tokens.head != "load") error("Not a load command")
|
||||
val args = commandString.split("""\s+""").toSeq
|
||||
val srcs = sourcepath(args)
|
||||
val nme = name(args)
|
||||
|
||||
lazy val cp = classpath(args)
|
||||
lazy val mod = module(args)
|
||||
lazy val proj = project(args).getOrElse(base)
|
||||
|
||||
if(!srcs.isEmpty)
|
||||
SourceLoad(cp, srcs, output(args), mod, auto(args), nme)
|
||||
else if(!cp.isEmpty)
|
||||
BinaryLoad(cp, mod, nme)
|
||||
else
|
||||
{
|
||||
val args = tokens.drop(1)
|
||||
val srcs = sourcepath(args)
|
||||
val nme = name(args)
|
||||
|
||||
lazy val cp = classpath(args)
|
||||
lazy val mod = module(args)
|
||||
lazy val proj = project(args).getOrElse(base)
|
||||
|
||||
if(!srcs.isEmpty)
|
||||
SourceLoad(cp, srcs, output(args), mod, auto(args), nme)
|
||||
else if(!cp.isEmpty)
|
||||
BinaryLoad(cp, mod, nme)
|
||||
else
|
||||
ProjectLoad(proj, nme)
|
||||
}
|
||||
ProjectLoad(proj, auto(args), nme)
|
||||
}
|
||||
|
||||
def auto(args: Seq[String]): Auto.Value =
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
|||
val collectionSub = testedBase(utilPath / "collection", "Collections")
|
||||
val ioSub = testedBase(utilPath / "io", "IO", controlSub)
|
||||
val classpathSub = baseProject(utilPath / "classpath", "Classpath", launchInterfaceSub, ioSub)
|
||||
val completeSub = testedBase(utilPath / "complete", "Completion", ioSub)
|
||||
val completeSub = project(utilPath / "complete", "Completion", new InputProject(_), controlSub, ioSub)
|
||||
val logSub = project(utilPath / "log", "Logging", new LogProject(_), interfaceSub)
|
||||
val classfileSub = testedBase(utilPath / "classfile", "Classfile", ioSub, interfaceSub, logSub)
|
||||
val datatypeSub = baseProject(utilPath /"datatype", "Datatype Generator", ioSub)
|
||||
|
|
@ -102,6 +102,10 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
|||
override def deliverProjectDependencies = Nil
|
||||
}
|
||||
}
|
||||
class InputProject(info: ProjectInfo) extends TestedBase(info)
|
||||
{
|
||||
val jline = jlineDep
|
||||
}
|
||||
class WebAppProject(info: ProjectInfo) extends Base(info)
|
||||
{
|
||||
val jetty = "org.mortbay.jetty" % "jetty" % "6.1.14" % "optional"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2008, 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
import jline.{Completor, ConsoleReader}
|
||||
abstract class JLine extends LineReader
|
||||
{
|
||||
protected[this] val reader: ConsoleReader
|
||||
def readLine(prompt: String) = JLine.withJLine { unsynchronizedReadLine(prompt) }
|
||||
private[this] def unsynchronizedReadLine(prompt: String) =
|
||||
reader.readLine(prompt) match
|
||||
{
|
||||
case null => None
|
||||
case x => Some(x.trim)
|
||||
}
|
||||
}
|
||||
private object JLine
|
||||
{
|
||||
def terminal = jline.Terminal.getTerminal
|
||||
def resetTerminal() = withTerminal { _ => jline.Terminal.resetTerminal }
|
||||
private def withTerminal[T](f: jline.Terminal => T): T =
|
||||
synchronized
|
||||
{
|
||||
val t = terminal
|
||||
t.synchronized { f(t) }
|
||||
}
|
||||
def createReader() =
|
||||
withTerminal { t =>
|
||||
val cr = new ConsoleReader
|
||||
t.enableEcho()
|
||||
cr.setBellEnabled(false)
|
||||
cr
|
||||
}
|
||||
def withJLine[T](action: => T): T =
|
||||
withTerminal { t =>
|
||||
t.disableEcho()
|
||||
try { action }
|
||||
finally { t.enableEcho() }
|
||||
}
|
||||
private[sbt] def initializeHistory(cr: ConsoleReader, historyPath: Option[Path]): Unit =
|
||||
for(historyLocation <- historyPath)
|
||||
{
|
||||
val historyFile = historyLocation.asFile
|
||||
ErrorHandling.wideConvert
|
||||
{
|
||||
historyFile.getParentFile.mkdirs()
|
||||
val history = cr.getHistory
|
||||
history.setMaxSize(MaxHistorySize)
|
||||
history.setHistoryFile(historyFile)
|
||||
}
|
||||
}
|
||||
def simple(historyPath: Option[Path]): SimpleReader = new SimpleReader(historyPath)
|
||||
val MaxHistorySize = 500
|
||||
}
|
||||
|
||||
trait LineReader extends NotNull
|
||||
{
|
||||
def readLine(prompt: String): Option[String]
|
||||
}
|
||||
private[sbt] final class LazyJLineReader(historyPath: Option[Path], 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
|
||||
}
|
||||
}
|
||||
private class LazyCompletor(delegate0: => Completor) extends Completor
|
||||
{
|
||||
private lazy val delegate = delegate0
|
||||
def complete(buffer: String, cursor: Int, candidates: java.util.List[_]): Int =
|
||||
delegate.complete(buffer, cursor, candidates)
|
||||
}
|
||||
|
||||
class SimpleReader private[sbt] (historyPath: Option[Path]) extends JLine
|
||||
{
|
||||
protected[this] val reader = JLine.createReader()
|
||||
JLine.initializeHistory(reader, historyPath)
|
||||
}
|
||||
object SimpleReader extends JLine
|
||||
{
|
||||
protected[this] val reader = JLine.createReader()
|
||||
}
|
||||
Loading…
Reference in New Issue