Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

239 lines (208 sloc) 10.424 kb
package sbt
import complete.{DefaultParsers, HistoryCommands, Parser}
import classpath.ClasspathUtilities.toLoader
import DefaultParsers._
import Types.{const,idFun}
import Function.tupled
import Command.applyEffect
import State.FailureWall
import HistoryCommands.{Start => HistoryPrefix}
import BasicCommandStrings._
import CommandUtil._
import BasicKeys._
import java.io.File
object BasicCommands
{
lazy val allBasicCommands = Seq(nop, ignore, help, multi, ifLast, append, setOnFailure, clearOnFailure, reboot, call, exit, continuous, history, shell, read, alias)
def nop = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun)
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State) =
{
val h = (Help.empty /: s.definedCommands)(_ ++ _.help(s))
val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).?
applyEffect(spacedArg)(runHelp(s, h))
}
def runHelp(s: State, h: Help)(arg: Option[String]): State =
{
val message = arg match {
case Some(x) => detail(x, h.detail)
case None =>
val brief = aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
val more = h.more.toSeq.sorted
if(more.isEmpty)
brief
else
brief + "\n" + moreHelp(more)
}
System.out.println(message)
s
}
def moreHelp(more: Seq[String]): String =
more.mkString("More command help available using 'help <command>' for:\n ", ", ", "\n")
def multiParser(s: State): Parser[Seq[String]] =
{
val nonSemi = token(charClass(_ != ';').+, hide= const(true))
( token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser&nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim) ).+
}
def multiApplied(s: State) =
Command.applyEffect( multiParser(s) )( _ ::: s )
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed) )
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, any.+) )
def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide= const(true)))
def ifLast = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser) { (s, arg) =>
if(s.remainingCommands.isEmpty) arg :: s else s
}
def append = Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser) { (s, arg) =>
s.copy(remainingCommands = s.remainingCommands :+ arg)
}
def setOnFailure = Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser) { (s, arg) =>
s.copy(onFailure = Some(arg))
}
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def reboot = Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser) { (s, full) =>
s.reboot(full)
}
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
def call = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) { case (state,(cp,args)) =>
val parentLoader = getClass.getClassLoader
state.log.info("Applying State transformations " + args.mkString(", ") + (if(cp.isEmpty) "" else " from " + cp.mkString(File.separator)))
val loader = if(cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader)
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
}
def callParser: Parser[(Seq[String], Seq[String])] = token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
private[this] def className: Parser[String] =
token(StringBasic.filter(s => !s.contains("-"), const("- not allowed in class name")), "<class name>")
private[this] def classpathOptionParser: Parser[Seq[String]] =
token( ("-cp" | "-classpath") ~> Space ) ~> rep1sep(classpathString, token(File.separatorChar)) <~ token(Space)
private[this] def classpathString: Parser[String] =
token(charClass(entryClass).+.string, "<classpath-entry>")
private[this] def entryClass(c: Char): Boolean =
c != File.separatorChar && !java.lang.Character.isWhitespace(c)
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
def continuous =
Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(otherCommandParser) { (s, arg) =>
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
val repeat = ContinuousExecutePrefix + (if(arg.startsWith(" ")) arg else " " + arg)
Watched.executeContinuously(w, s, arg, repeat)
}
}
def history = Command.custom(historyParser, BasicCommandStrings.historyHelp)
def historyParser(s: State): Parser[() => State] =
Command.applyEffect(HistoryCommands.actionParser) { histFun =>
val logError = (msg: String) => s.log.error(msg)
val hp = s get historyPath getOrElse None
val lines = hp.toList.flatMap( p => IO.readLines(p) ).toIndexedSeq
histFun( complete.History(lines, hp, logError) ) match
{
case Some(commands) =>
commands foreach println //printing is more appropriate than logging
(commands ::: s).continue
case None => s.fail
}
}
def shell = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s =>
val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history"))
val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser)
val line = reader.readLine(prompt)
line match {
case Some(line) =>
val newState = s.copy(onFailure = Some(Shell), remainingCommands = line +: Shell +: s.remainingCommands).setInteractive(true)
if(line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s.setInteractive(false)
}
}
def read = Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s)) )
def readParser(s: State) =
{
val files = (token(Space) ~> fileParser(s.baseDir)).+
val portAndSuccess = token(OptSpace) ~> Port
portAndSuccess || files
}
def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
arg match
{
case Left(portAndSuccess) =>
val port = math.abs(portAndSuccess)
val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match
{
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(ReadCommand + " " + (-port)))
case None =>
System.err.println("Connection closed.")
s.fail
}
case Right(from) =>
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 {
s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
s
}
}
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
{
// split into two connections because this first connection ends the previous communication
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
// and this second connection starts the next communication
xsbt.IPC.client(port) { ipc =>
val message = ipc.receive
if(message eq null) None else Some(message)
}
}
def alias = Command.make(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s =>
val name = token(OpOrID.examples( aliasNames(s) : _*) )
val assign = token(OptSpace ~ '=' ~ OptSpace)
val sfree = removeAliases(s)
val to = matched(sfree.combinedParser, partial = true).failOnException | any.+.string
val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?)
applyEffect(base)(t => runAlias(s, t) )
}
def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State =
args match
{
case None => printAliases(s); s
case Some(x ~ None) if !x.isEmpty => printAlias(s, x.trim); s
case Some(name ~ Some(None)) => removeAlias(s, name.trim)
case Some(name ~ Some(Some(value))) => addAlias(s, name.trim, value.trim)
}
def addAlias(s: State, name: String, value: String): State =
if(Command validID name) {
val removed = removeAlias(s, name)
if(value.isEmpty) removed else removed.copy(definedCommands = newAlias(name, value) +: removed.definedCommands)
} else {
System.err.println("Invalid alias name '" + name + "'.")
s.fail
}
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey)
def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c)) )
def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag))
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => ! (c.tags contains tag))
def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c))
def isNamed(name: String, alias: Option[(String,String)]): Boolean = alias match { case None => false; case Some((n,_)) => name == n }
def getAlias(c: Command): Option[(String,String)] = c.tags get CommandAliasKey
def printAlias(s: State, name: String): Unit = printAliases(aliases(s,(n,v) => n == name) )
def printAliases(s: State): Unit = printAliases(allAliases(s))
def printAliases(as: Seq[(String,String)]): Unit =
for( (name,value) <- as)
println("\t" + name + " = " + value)
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
def allAliases(s: State): Seq[(String,String)] = aliases(s, (n,v) => true)
def aliases(s: State, pred: (String,String) => Boolean): Seq[(String,String)] =
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
def newAlias(name: String, value: String): Command =
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] =
OptSpace ~> Parser(Command.combine(removeAlias(state,name).definedCommands)(state))(value)
def delegateToAlias(name: String, orElse: Parser[() => State])(state: State): Parser[() => State] =
aliases(state, (nme,_) => nme == name).headOption match {
case None => orElse
case Some((n,v)) => aliasBody(n,v)(state)
}
val CommandAliasKey = AttributeKey[(String,String)]("is-command-alias", "Internal: marker for Commands created as aliases for another command.")
}
Jump to Line
Something went wrong with that request. Please try again.