adding more commands

This commit is contained in:
Mark Harrah 2010-07-27 23:01:45 -04:00
parent 8e255bc226
commit 767a1e47c1
11 changed files with 596 additions and 111 deletions

View File

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

View File

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

View File

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

157
main/CommandSupport.scala Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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