mirror of https://github.com/sbt/sbt.git
223 lines
9.0 KiB
Scala
223 lines
9.0 KiB
Scala
package sbt
|
|
|
|
import complete.{DefaultParsers, HistoryCommands, Parser}
|
|
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 args = (token(Space) ~> token( NotSpace examples helpCommands )).*
|
|
applyEffect(args)(runHelp(s, h))
|
|
}
|
|
|
|
def runHelp(s: State, h: Help)(args: Seq[String]): State =
|
|
{
|
|
val message =
|
|
if(args.isEmpty)
|
|
aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
|
|
else
|
|
detail(args, h.detail) mkString("\n", "\n\n", "\n")
|
|
System.out.println(message)
|
|
s
|
|
}
|
|
def detail(selected: Seq[String], detailMap: Map[String, String]): Seq[String] =
|
|
selected.distinct flatMap { detailMap get _ }
|
|
|
|
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, IfLastBrief, IfLastDetailed)(otherCommandParser) { (s, arg) =>
|
|
if(s.remainingCommands.isEmpty) arg :: s else s
|
|
}
|
|
def append = Command(AppendCommand, AppendLastBrief, AppendLastDetailed)(otherCommandParser) { (s, arg) =>
|
|
s.copy(remainingCommands = s.remainingCommands :+ arg)
|
|
}
|
|
|
|
def setOnFailure = Command(OnFailure, OnFailureBrief, OnFailureDetailed)(otherCommandParser) { (s, arg) =>
|
|
s.copy(onFailure = Some(arg))
|
|
}
|
|
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
|
|
|
|
def reboot = Command(RebootCommand, RebootBrief, RebootDetailed)(rebootParser) { (s, full) =>
|
|
s.reboot(full)
|
|
}
|
|
def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false
|
|
|
|
def call = Command(ApplyCommand, ApplyBrief, ApplyDetailed)(_ => spaceDelimited("<class name>")) { (state,args) =>
|
|
val loader = getClass.getClassLoader
|
|
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader))
|
|
(state /: loaded) { case (s, obj: (State => State)) => obj(s) }
|
|
}
|
|
|
|
def exit = Command.command(TerminateAction, exitBrief, exitBrief ) ( _ exit true )
|
|
|
|
|
|
def continuous =
|
|
Command(ContinuousExecutePrefix, Help(continuousBriefHelp) )(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, ShellBrief, 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)
|
|
if(line.trim.isEmpty) newState else newState.clearGlobalLog
|
|
case None => s
|
|
}
|
|
}
|
|
|
|
def read = Command.make(ReadCommand, ReadBrief, 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, AliasBrief, 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) | 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.")
|
|
} |