Cleanup Command/Help and usage

This commit is contained in:
Dale Wijnand 2017-04-06 10:53:12 +01:00
parent 2caa324d52
commit 6c07972dd0
No known key found for this signature in database
GPG Key ID: 4F256E3D151DF5EF
13 changed files with 603 additions and 357 deletions

View File

@ -17,15 +17,15 @@ object BasicCommandStrings {
val TerminateAction: String = Exit val TerminateAction: String = Exit
def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand <command>').") def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand <command>').")
def helpDetailed = HelpCommand + """ def helpDetailed = s"""$HelpCommand
Prints a help summary. Prints a help summary.
""" + HelpCommand + """ <command> $HelpCommand <command>
Prints detailed help for command <command>. Prints detailed help for command <command>.
""" + HelpCommand + """ <regular expression> $HelpCommand <regular expression>
Searches the help according to the provided regular expression. Searches the help according to the provided regular expression.
""" """
@ -50,6 +50,7 @@ object BasicCommandStrings {
val detailed = levels.map(l => (l.toString, logLevelDetail(l))).toMap val detailed = levels.map(l => (l.toString, logLevelDetail(l))).toMap
Help(brief, detailed) Help(brief, detailed)
} }
private[this] def logLevelDetail(level: Level.Value): String = private[this] def logLevelDetail(level: Level.Value): String =
s"""$level s"""$level
@ -130,24 +131,21 @@ object BasicCommandStrings {
val AliasCommand = "alias" val AliasCommand = "alias"
def AliasDetailed = def AliasDetailed =
AliasCommand + """ s"""$AliasCommand
Prints a list of defined aliases. Prints a list of defined aliases.
""" + $AliasCommand name
AliasCommand + """ name
Prints the alias defined for `name`. Prints the alias defined for `name`.
""" + $AliasCommand name=value
AliasCommand + """ name=value
Sets the alias `name` to `value`, replacing any existing alias with that name. Sets the alias `name` to `value`, replacing any existing alias with that name.
Whenever `name` is entered, the corresponding `value` is run. Whenever `name` is entered, the corresponding `value` is run.
If any argument is provided to `name`, it is appended as argument to `value`. If any argument is provided to `name`, it is appended as argument to `value`.
""" + $AliasCommand name=
AliasCommand + """ name=
Removes the alias for `name`.""" Removes the alias for `name`."""
@ -194,9 +192,9 @@ object BasicCommandStrings {
def IfLast = "iflast" def IfLast = "iflast"
def IfLastCommon = "If there are no more commands after this one, 'command' is run." def IfLastCommon = "If there are no more commands after this one, 'command' is run."
def IfLastDetailed = def IfLastDetailed =
IfLast + """ <command> s"""$IfLast <command>
""" + IfLastCommon $IfLastCommon"""
val ContinuousExecutePrefix = "~" val ContinuousExecutePrefix = "~"
def continuousDetail = "Executes the specified command whenever source files change." def continuousDetail = "Executes the specified command whenever source files change."

View File

@ -2,7 +2,15 @@ package sbt
import sbt.util.Level import sbt.util.Level
import sbt.internal.util.{ AttributeKey, FullReader } import sbt.internal.util.{ AttributeKey, FullReader }
import sbt.internal.util.complete.{ Completion, Completions, DefaultParsers, History => CHistory, HistoryCommands, Parser, TokenCompletions } import sbt.internal.util.complete.{
Completion,
Completions,
DefaultParsers,
History => CHistory,
HistoryCommands,
Parser,
TokenCompletions
}
import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.inc.classpath.ClasspathUtilities.toLoader import sbt.internal.inc.classpath.ClasspathUtilities.toLoader
import sbt.internal.inc.ModuleUtilities import sbt.internal.inc.ModuleUtilities
@ -19,32 +27,34 @@ import sbt.io.IO
import scala.util.control.NonFatal import scala.util.control.NonFatal
object BasicCommands { object BasicCommands {
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure, lazy val allBasicCommands: Seq[Command] = Seq(
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, client, read, alias) ++ compatCommands nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, client,
read, alias
) ++ compatCommands
def nop = Command.custom(s => success(() => s)) def nop: Command = Command.custom(s => success(() => s))
def ignore = Command.command(FailureWall)(idFun) def ignore: Command = Command.command(FailureWall)(idFun)
def early: Command = Command.arb(earlyParser, earlyHelp)((s, other) => other :: s)
def early = Command.arb(earlyParser, earlyHelp) { (s, other) => other :: s }
private[this] def levelParser: Parser[String] = private[this] def levelParser: Parser[String] =
token(Level.Debug.toString) | token(Level.Info.toString) | token(Level.Warn.toString) | token(Level.Error.toString) Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _)
private[this] def earlyParser: State => Parser[String] = (s: State) =>
(token(EarlyCommand + "(") flatMap { _ => private[this] def earlyParser: State => Parser[String] = (s: State) => {
otherCommandParser(s) <~ token(")") val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")"))
}) | val p2 = token("-") flatMap (_ => levelParser)
(token("-") flatMap { _ => p1 | p2
levelParser }
})
private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed) private[this] def earlyHelp = Help(EarlyCommand, EarlyCommandBrief, EarlyCommandDetailed)
def help = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser) def help: Command = Command.make(HelpCommand, helpBrief, helpDetailed)(helpParser)
def helpParser(s: State) = def helpParser(s: State): Parser[() => State] =
{ {
val h = (Help.empty /: s.definedCommands) { (a, b) => val h = (Help.empty /: s.definedCommands)((a, b) =>
a ++ a ++ (try b.help(s) catch { case NonFatal(_) => Help.empty }))
(try b.help(s) catch { case NonFatal(ex) => Help.empty })
}
val helpCommands = h.detail.keySet val helpCommands = h.detail.keySet
val spacedArg = singleArgument(helpCommands).? val spacedArg = singleArgument(helpCommands).?
applyEffect(spacedArg)(runHelp(s, h)) applyEffect(spacedArg)(runHelp(s, h))
@ -52,24 +62,22 @@ object BasicCommands {
def runHelp(s: State, h: Help)(arg: Option[String]): State = def runHelp(s: State, h: Help)(arg: Option[String]): State =
{ {
val message = try val message = try Help.message(h, arg) catch { case NonFatal(ex) => ex.toString }
Help.message(h, arg)
catch {
case NonFatal(ex) =>
ex.toString
}
System.out.println(message) System.out.println(message)
s s
} }
def completionsCommand = Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser) def completionsCommand: Command =
def completionsParser(state: State) = Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)
def completionsParser(state: State): Parser[() => State] =
{ {
val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s } val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s }
val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted)
applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state)) applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state))
} }
def runCompletions(state: State)(input: String): State = { def runCompletions(state: State)(input: String): State = {
Parser.completions(state.combinedParser, input, 9).get map { Parser.completions(state.combinedParser, input, 9).get map {
c => if (c.isEmpty) input else input + c.append c => if (c.isEmpty) input else input + c.append
@ -82,34 +90,40 @@ object BasicCommands {
def multiParser(s: State): Parser[List[String]] = def multiParser(s: State): Parser[List[String]] =
{ {
val nonSemi = token(charClass(_ != ';').+, hide = const(true)) val nonSemi = token(charClass(_ != ';').+, hide = const(true))
(token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim)).+ map { _.toList } val semi = token(';' ~> OptSpace)
val part = semi flatMap (_ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace))
(part map (_.trim)).+ map (_.toList)
} }
def multiApplied(s: State): Parser[() => State] = def multiApplied(s: State): Parser[() => State] =
Command.applyEffect(multiParser(s))(_ ::: s) Command.applyEffect(multiParser(s))(_ ::: s)
def multi = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed)) def multi: Command = Command.custom(multiApplied, Help(Multi, MultiBrief, MultiDetailed))
lazy val otherCommandParser: State => Parser[String] =
(s: State) => token(OptSpace ~> combinedLax(s, NotSpaceClass ~ any.*))
lazy val otherCommandParser = (s: State) => token(OptSpace ~> combinedLax(s, NotSpaceClass ~ any.*))
def combinedLax(s: State, any: Parser[_]): Parser[String] = def combinedLax(s: State, any: Parser[_]): Parser[String] =
matched(s.combinedParser | token(any, hide = const(true))) matched(s.combinedParser | token(any, hide = const(true)))
def ifLast = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser) { (s, arg) => def ifLast: Command = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) =>
if (s.remainingCommands.isEmpty) arg :: s else s if (s.remainingCommands.isEmpty) arg :: s else s)
}
def append = Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser) { (s, arg) => def append: Command =
s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)) Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser)((s, arg) =>
} s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)))
def setOnFailure: Command =
Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser)((s, arg) =>
s.copy(onFailure = Some(Exec(arg, s.source))))
def setOnFailure = Command(OnFailure, Help.more(OnFailure, OnFailureDetailed))(otherCommandParser) { (s, arg) =>
s.copy(onFailure = Some(Exec(arg, s.source)))
}
private[sbt] def compatCommands = Seq( private[sbt] def compatCommands = Seq(
Command.command(Compat.ClearOnFailure) { s => Command.command(Compat.ClearOnFailure) { s =>
s.log.warn(Compat.ClearOnFailureDeprecated) s.log.warn(Compat.ClearOnFailureDeprecated)
s.copy(onFailure = None) s.copy(onFailure = None)
}, },
Command.arb(s => token(Compat.OnFailure, hide = const(true)).flatMap(x => otherCommandParser(s))) { (s, arg) => Command.arb(s => token(Compat.OnFailure, hide = const(true))
.flatMap(_ => otherCommandParser(s))) { (s, arg) =>
s.log.warn(Compat.OnFailureDeprecated) s.log.warn(Compat.OnFailureDeprecated)
s.copy(onFailure = Some(Exec(arg, s.source))) s.copy(onFailure = Some(Exec(arg, s.source)))
}, },
@ -119,43 +133,55 @@ object BasicCommands {
} }
) )
def clearOnFailure = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) def clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None))
def stashOnFailure = Command.command(StashOnFailure)(s => s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def popOnFailure = Command.command(PopOnFailure) { s => def stashOnFailure: Command = Command.command(StashOnFailure)(s =>
s.copy(onFailure = None).update(OnFailureStack)(s.onFailure :: _.toList.flatten))
def popOnFailure: Command = Command.command(PopOnFailure) { s =>
val stack = s.get(OnFailureStack).getOrElse(Nil) val stack = s.get(OnFailureStack).getOrElse(Nil)
val updated = if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail) val updated = if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail)
updated.copy(onFailure = stack.headOption.flatten) updated.copy(onFailure = stack.headOption.flatten)
} }
def reboot = Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser) { (s, full) => def reboot: Command =
s.reboot(full) 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) { def rebootParser(s: State): Parser[Boolean] = token(Space ~> "full" ^^^ true) ?? false
def call: Command = Command(ApplyCommand, Help.more(ApplyCommand, ApplyDetailed))(_ => callParser) {
case (state, (cp, args)) => case (state, (cp, args)) =>
val parentLoader = getClass.getClassLoader val parentLoader = getClass.getClassLoader
state.log.info("Applying State transformations " + args.mkString(", ") + (if (cp.isEmpty) "" else " from " + cp.mkString(File.pathSeparator))) def argsStr = args mkString ", "
def cpStr = cp mkString File.pathSeparator
def fromCpStr = if (cp.isEmpty) "" else s" from $cpStr"
state.log info s"Applying State transformations $argsStr$fromCpStr"
val loader = if (cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader) val loader = if (cp.isEmpty) parentLoader else toLoader(cp.map(f => new File(f)), parentLoader)
val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State]) val loaded = args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State])
(state /: loaded)((s, obj) => obj(s)) (state /: loaded)((s, obj) => obj(s))
} }
def callParser: Parser[(Seq[String], Seq[String])] = token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
def callParser: Parser[(Seq[String], Seq[String])] =
token(Space) ~> ((classpathOptionParser ?? Nil) ~ rep1sep(className, token(Space)))
private[this] def className: Parser[String] = private[this] def className: Parser[String] =
{ {
val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.") val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.")
def single(s: String) = Completions.single(Completion.displayOnly(s)) def single(s: String) = Completions.single(Completion.displayOnly(s))
val compl = TokenCompletions.fixed((seen, level) => if (seen.startsWith("-")) Completions.nil else single("<class name>")) val compl = TokenCompletions.fixed((seen, _) =>
if (seen.startsWith("-")) Completions.nil else single("<class name>"))
token(base, compl) token(base, compl)
} }
private[this] def classpathOptionParser: Parser[Seq[String]] = private[this] def classpathOptionParser: Parser[Seq[String]] =
token(("-cp" | "-classpath") ~> Space) ~> classpathStrings <~ token(Space) token(("-cp" | "-classpath") ~> Space) ~> classpathStrings <~ token(Space)
private[this] def classpathStrings: Parser[Seq[String]] = private[this] def classpathStrings: Parser[Seq[String]] =
token(StringBasic.map(s => IO.pathSplit(s).toSeq), "<classpath>") token(StringBasic.map(s => IO.pathSplit(s).toSeq), "<classpath>")
def exit = Command.command(TerminateAction, exitBrief, exitBrief)(_ exit true) def exit: Command = Command.command(TerminateAction, exitBrief, exitBrief)(_ exit true)
def continuous = def continuous: Command =
Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(otherCommandParser) { (s, arg) => Command(ContinuousExecutePrefix, continuousBriefHelp, continuousDetail)(otherCommandParser) { (s, arg) =>
withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w => withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w =>
val repeat = ContinuousExecutePrefix + (if (arg.startsWith(" ")) arg else " " + arg) val repeat = ContinuousExecutePrefix + (if (arg.startsWith(" ")) arg else " " + arg)
@ -163,7 +189,8 @@ object BasicCommands {
} }
} }
def history = Command.custom(historyParser, BasicCommandStrings.historyHelp) def history: Command = Command.custom(historyParser, BasicCommandStrings.historyHelp)
def historyParser(s: State): Parser[() => State] = def historyParser(s: State): Parser[() => State] =
Command.applyEffect(HistoryCommands.actionParser) { histFun => Command.applyEffect(HistoryCommands.actionParser) { histFun =>
val logError = (msg: String) => s.log.error(msg) val logError = (msg: String) => s.log.error(msg)
@ -177,23 +204,27 @@ object BasicCommands {
} }
} }
def shell = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s => def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s =>
val history = (s get historyPath) getOrElse Some(new File(s.baseDir, ".history")) 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 prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser) val reader = new FullReader(history, s.combinedParser)
val line = reader.readLine(prompt) val line = reader.readLine(prompt)
line match { line match {
case Some(line) => case Some(line) =>
val newState = s.copy(onFailure = Some(Exec(Shell, None)), remainingCommands = Exec(line, s.source) +: Exec(Shell, None) +: s.remainingCommands).setInteractive(true) val newState = s.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = Exec(line, s.source) +: Exec(Shell, None) +: s.remainingCommands
).setInteractive(true)
if (line.trim.isEmpty) newState else newState.clearGlobalLog if (line.trim.isEmpty) newState else newState.clearGlobalLog
case None => s.setInteractive(false) case None => s.setInteractive(false)
} }
} }
def client = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser) def client: Command = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser)
def clientParser(s0: State) =
def clientParser(s0: State): Parser[() => State] =
{ {
val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map { case _ => Nil }) val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil))
applyEffect(p)({ inputArg => applyEffect(p)({ inputArg =>
val arguments = inputArg.toList ++ val arguments = inputArg.toList ++
(s0.remainingCommands match { (s0.remainingCommands match {
@ -205,20 +236,26 @@ object BasicCommands {
}) })
} }
def read = Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s))) def read: Command =
def readParser(s: State) = Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s)))
def readParser(s: State): Parser[Either[Int, Seq[File]]] =
{ {
val files = (token(Space) ~> fileParser(s.baseDir)).+ val files = (token(Space) ~> fileParser(s.baseDir)).+
val portAndSuccess = token(OptSpace) ~> Port val portAndSuccess = token(OptSpace) ~> Port
portAndSuccess || files portAndSuccess || files
} }
def doRead(s: State)(arg: Either[Int, Seq[File]]): State = def doRead(s: State)(arg: Either[Int, Seq[File]]): State =
arg match { arg match {
case Left(portAndSuccess) => case Left(portAndSuccess) =>
val port = math.abs(portAndSuccess) val port = math.abs(portAndSuccess)
val previousSuccess = portAndSuccess >= 0 val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match { readMessage(port, previousSuccess) match {
case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(Exec(ReadCommand + " " + (-port), s.source))) case Some(message) =>
(message :: (ReadCommand + " " + port) :: s).copy(
onFailure = Some(Exec(ReadCommand + " " + (-port), s.source))
)
case None => case None =>
System.err.println("Connection closed.") System.err.println("Connection closed.")
s.fail s.fail
@ -226,12 +263,14 @@ object BasicCommands {
case Right(from) => case Right(from) =>
val notFound = notReadable(from) val notFound = notReadable(from)
if (notFound.isEmpty) if (notFound.isEmpty)
readLines(from).toList ::: s // this means that all commands from all files are loaded, parsed, and inserted before any are executed // this means that all commands from all files are loaded, parsed, & inserted before any are executed
readLines(from).toList ::: s
else { else {
s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t")) s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t"))
s s
} }
} }
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] = private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
{ {
// split into two connections because this first connection ends the previous communication // split into two connections because this first connection ends the previous communication
@ -243,7 +282,7 @@ object BasicCommands {
} }
} }
def alias = Command.make(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s => def alias: Command = Command.make(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s =>
val name = token(OpOrID.examples(aliasNames(s): _*)) val name = token(OpOrID.examples(aliasNames(s): _*))
val assign = token(OptSpace ~ '=' ~ OptSpace) val assign = token(OptSpace ~ '=' ~ OptSpace)
val sfree = removeAliases(s) val sfree = removeAliases(s)
@ -273,34 +312,45 @@ object BasicCommands {
s.copy(definedCommands = newAlias(name, value) +: s.definedCommands) s.copy(definedCommands = newAlias(name, value) +: s.definedCommands)
def removeAliases(s: State): State = removeTagged(s, CommandAliasKey) 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 removeAlias(s: State, name: String): State =
def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => !(c.tags contains tag)) 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 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 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 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 printAlias(s: State, name: String): Unit = printAliases(aliases(s, (n, _) => n == name))
def printAliases(s: State): Unit = printAliases(allAliases(s)) def printAliases(s: State): Unit = printAliases(allAliases(s))
def printAliases(as: Seq[(String, String)]): Unit = def printAliases(as: Seq[(String, String)]): Unit =
for ((name, value) <- as) for ((name, value) <- as)
println("\t" + name + " = " + value) println("\t" + name + " = " + value)
def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1) def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1)
def allAliases(s: State): Seq[(String, String)] = aliases(s, (n, v) => true) def allAliases(s: State): Seq[(String, String)] = aliases(s, (_, _) => true)
def aliases(s: State, pred: (String, String) => Boolean): Seq[(String, String)] = def aliases(s: State, pred: (String, String) => Boolean): Seq[(String, String)] =
s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred))) s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred)))
def newAlias(name: String, value: String): Command = def newAlias(name: String, value: String): Command =
Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value)) Command.make(name, (name, s"'$value'"), s"Alias of '$value'")(aliasBody(name, value))
.tag(CommandAliasKey, (name, value))
def aliasBody(name: String, value: String)(state: State): Parser[() => State] = { def aliasBody(name: String, value: String)(state: State): Parser[() => State] = {
val aliasRemoved = removeAlias(state, name) val aliasRemoved = removeAlias(state, name)
// apply the alias value to the commands of `state` except for the alias to avoid recursion (#933) // apply the alias value to the commands of `state` except for the alias to avoid recursion (#933)
val partiallyApplied = Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value) val partiallyApplied = Parser(Command.combine(aliasRemoved.definedCommands)(aliasRemoved))(value)
val arg = matched(partiallyApplied & (success(()) | (SpaceClass ~ any.*))) val arg = matched(partiallyApplied & (success(()) | (SpaceClass ~ any.*)))
// by scheduling the expanded alias instead of directly executing, we get errors on the expanded string (#598) // by scheduling the expanded alias instead of directly executing,
// we get errors on the expanded string (#598)
arg.map(str => () => (value + str) :: state) arg.map(str => () => (value + str) :: state)
} }
@ -310,5 +360,9 @@ object BasicCommands {
case Some((n, v)) => aliasBody(n, v)(state) 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.") val CommandAliasKey: AttributeKey[(String, String)] =
AttributeKey[(String, String)](
"is-command-alias",
"Internal: marker for Commands created as aliases for another command."
)
} }

View File

@ -8,98 +8,155 @@ import sbt.internal.util.complete.{ DefaultParsers, EditDistance, Parser }
import sbt.internal.util.Types.const import sbt.internal.util.Types.const
import sbt.internal.util.{ AttributeKey, AttributeMap, Util } import sbt.internal.util.{ AttributeKey, AttributeMap, Util }
/**
* An operation that can be executed from the sbt console.
*
* <p>The operation takes a [[sbt.State State]] as a parameter and returns a [[sbt.State State]].
* This means that a command can look at or modify other sbt settings, for example.
* Typically you would resort to a command when you need to do something that's impossible in a regular task.
*/
sealed trait Command { sealed trait Command {
def help: State => Help def help: State => Help
def parser: State => Parser[() => State] def parser: State => Parser[() => State]
def tags: AttributeMap def tags: AttributeMap
def tag[T](key: AttributeKey[T], value: T): Command def tag[T](key: AttributeKey[T], value: T): Command
} }
private[sbt] final class SimpleCommand(val name: String, private[sbt] val help0: Help, val parser: State => Parser[() => State], val tags: AttributeMap) extends Command {
assert(Command validID name, "'" + name + "' is not a valid command name.") private[sbt] final class SimpleCommand(
def tag[T](key: AttributeKey[T], value: T): SimpleCommand = new SimpleCommand(name, help0, parser, tags.put(key, value)) val name: String,
private[sbt] val help0: Help,
val parser: State => Parser[() => State],
val tags: AttributeMap
) extends Command {
assert(Command validID name, s"'$name' is not a valid command name.")
def help = const(help0) def help = const(help0)
def tag[T](key: AttributeKey[T], value: T): SimpleCommand =
new SimpleCommand(name, help0, parser, tags.put(key, value))
override def toString = s"SimpleCommand($name)" override def toString = s"SimpleCommand($name)"
} }
private[sbt] final class ArbitraryCommand(val parser: State => Parser[() => State], val help: State => Help, val tags: AttributeMap) extends Command {
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand = new ArbitraryCommand(parser, help, tags.put(key, value)) private[sbt] final class ArbitraryCommand(
val parser: State => Parser[() => State], val help: State => Help, val tags: AttributeMap
) extends Command {
def tag[T](key: AttributeKey[T], value: T): ArbitraryCommand =
new ArbitraryCommand(parser, help, tags.put(key, value))
} }
object Command { object Command {
import DefaultParsers._ import DefaultParsers._
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command = command(name, Help(name, (name, briefHelp), detail))(f) // Lowest-level command construction
def command(name: String, help: Help = Help.empty)(f: State => State): Command = make(name, help)(state => success(() => f(state)))
def make(name: String, help: Help = Help.empty)(parser: State => Parser[() => State]): Command =
new SimpleCommand(name, help, parser, AttributeMap.empty)
def make(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[() => State]): Command = def make(name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[() => State]): Command =
make(name, Help(name, briefHelp, detail))(parser) make(name, Help(name, briefHelp, detail))(parser)
def make(name: String, help: Help = Help.empty)(parser: State => Parser[() => State]): Command = new SimpleCommand(name, help, parser, AttributeMap.empty)
// General command construction
/** Construct a command with the given name, parser and effect. */
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(effect: (State, T) => State): Command =
make(name, help)(applyEffect(parser)(effect))
def apply[T](name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[T])(effect: (State, T) => State): Command = def apply[T](name: String, briefHelp: (String, String), detail: String)(parser: State => Parser[T])(effect: (State, T) => State): Command =
apply(name, Help(name, briefHelp, detail))(parser)(effect) apply(name, Help(name, briefHelp, detail))(parser)(effect)
def apply[T](name: String, help: Help = Help.empty)(parser: State => Parser[T])(effect: (State, T) => State): Command =
make(name, help)(applyEffect(parser)(effect)) // No-argument command construction
/** Construct a no-argument command with the given name and effect. */
def command(name: String, help: Help = Help.empty)(f: State => State): Command =
make(name, help)(state => success(() => f(state)))
def command(name: String, briefHelp: String, detail: String)(f: State => State): Command =
command(name, Help(name, (name, briefHelp), detail))(f)
// Single-argument command construction
/** Construct a single-argument command with the given name and effect. */
def single(name: String, help: Help = Help.empty)(f: (State, String) => State): Command =
make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state)))
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command =
single(name, Help(name, briefHelp, detail))(f)
// Multi-argument command construction
/** Construct a multi-argument command with the given name, tab completion display and effect. */
def args(name: String, display: String, help: Help = Help.empty)(f: (State, Seq[String]) => State): Command =
make(name, help)(state => spaceDelimited(display) map apply1(f, state))
def args(name: String, briefHelp: (String, String), detail: String, display: String)(f: (State, Seq[String]) => State): Command = def args(name: String, briefHelp: (String, String), detail: String, display: String)(f: (State, Seq[String]) => State): Command =
args(name, display, Help(name, briefHelp, detail))(f) args(name, display, Help(name, briefHelp, detail))(f)
def args(name: String, display: String, help: Help = Help.empty)(f: (State, Seq[String]) => State): Command = // create ArbitraryCommand
make(name, help)(state => spaceDelimited(display) map apply1(f, state))
def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command = def customHelp(parser: State => Parser[() => State], help: State => Help): Command =
single(name, Help(name, briefHelp, detail))(f) new ArbitraryCommand(parser, help, AttributeMap.empty)
def single(name: String, help: Help = Help.empty)(f: (State, String) => State): Command =
make(name, help)(state => token(trimmed(spacedAny(name)) map apply1(f, state)))
def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command = customHelp(parser, const(help)) def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command =
def customHelp(parser: State => Parser[() => State], help: State => Help): Command = new ArbitraryCommand(parser, help, AttributeMap.empty) customHelp(parser, const(help))
def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command = custom(applyEffect(parser)(effect), help)
def validID(name: String) = DefaultParsers.matches(OpOrID, name) def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command =
custom(applyEffect(parser)(effect), help)
// misc Command object utilities
def validID(name: String): Boolean = DefaultParsers.matches(OpOrID, name)
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] = p map (t => () => f(t))
def applyEffect[T](parser: State => Parser[T])(effect: (State, T) => State): State => Parser[() => State] = def applyEffect[T](parser: State => Parser[T])(effect: (State, T) => State): State => Parser[() => State] =
s => applyEffect(parser(s))(t => effect(s, t)) s => applyEffect(parser(s))(t => effect(s, t))
def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] =
p map { t => () => f(t) }
def combine(cmds: Seq[Command]): State => Parser[() => State] = def combine(cmds: Seq[Command]): State => Parser[() => State] = {
{ val (simple, arbs) = separateCommands(cmds)
val (simple, arbs) = separateCommands(cmds) state => (simpleParser(simple)(state) /: arbs.map(_ parser state))(_ | _)
state => (simpleParser(simple)(state) /: arbs.map(_ parser state)) { _ | _ } }
}
private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) = private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) =
Util.separate(cmds) { case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) } Util.separate(cmds) { case s: SimpleCommand => Left(s); case a: ArbitraryCommand => Right(a) }
private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C =
b => () => f(a, b) private[this] def apply1[A, B, C](f: (A, B) => C, a: A): B => () => C = b => () => f(a, b)
def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[() => State] = def simpleParser(cmds: Seq[SimpleCommand]): State => Parser[() => State] =
simpleParser(cmds.map(sc => (sc.name, argParser(sc))).toMap) simpleParser(cmds.map(sc => (sc.name, argParser(sc))).toMap)
private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] =
{ private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] = {
def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None) def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None)
s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s) s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s)
} }
def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] = def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
(state: State) => token(OpOrID examples commandMap.keys.toSet) flatMap { id => state => token(OpOrID examples commandMap.keys.toSet) flatMap (id =>
(commandMap get id) match { (commandMap get id) match {
case None => failure(invalidValue("command", commandMap.keys)(id)) case None => failure(invalidValue("command", commandMap.keys)(id))
case Some(c) => c(state) case Some(c) => c(state)
} })
}
def invalidValue(label: String, allowed: Iterable[String])(value: String): String = def invalidValue(label: String, allowed: Iterable[String])(value: String): String =
"Not a valid " + label + ": " + value + similar(value, allowed) s"Not a valid $label: $value" + similar(value, allowed)
def similar(value: String, allowed: Iterable[String]): String =
{ def similar(value: String, allowed: Iterable[String]): String = {
val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil
if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")") if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")")
} }
def suggestions(a: String, bs: Seq[String], maxDistance: Int = 3, maxSuggestions: Int = 3): Seq[String] = def suggestions(a: String, bs: Seq[String], maxDistance: Int = 3, maxSuggestions: Int = 3): Seq[String] =
bs.map { b => (b, distance(a, b)) } filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1) bs map (b => (b, distance(a, b))) filter (_._2 <= maxDistance) sortBy (_._2) take (maxSuggestions) map (_._1)
def distance(a: String, b: String): Int = def distance(a: String, b: String): Int =
EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1, matchCost = -1, caseCost = 1, true) EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1,
matchCost = -1, caseCost = 1, transpositions = true)
def spacedAny(name: String): Parser[String] = spacedC(name, any) def spacedAny(name: String): Parser[String] = spacedC(name, any)
def spacedC(name: String, c: Parser[Char]): Parser[String] = def spacedC(name: String, c: Parser[Char]): Parser[String] =
((c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString } ((c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString }
} }
@ -110,19 +167,25 @@ trait Help {
def more: Set[String] def more: Set[String]
def ++(o: Help): Help def ++(o: Help): Help
} }
private final class Help0(val brief: Seq[(String, String)], val detail: Map[String, String], val more: Set[String]) extends Help {
private final class Help0(
val brief: Seq[(String, String)], val detail: Map[String, String], val more: Set[String]
) extends Help {
def ++(h: Help): Help = new Help0(Help0.this.brief ++ h.brief, Help0.this.detail ++ h.detail, more ++ h.more) def ++(h: Help): Help = new Help0(Help0.this.brief ++ h.brief, Help0.this.detail ++ h.detail, more ++ h.more)
} }
object Help { object Help {
val empty: Help = briefDetail(Nil) val empty: Help = briefDetail(Nil)
def apply(name: String, briefHelp: (String, String), detail: String): Help = apply(briefHelp, Map((name, detail))) def apply(name: String, briefHelp: (String, String), detail: String): Help =
apply(briefHelp, Map((name, detail)))
def apply(briefHelp: (String, String), detailedHelp: Map[String, String] = Map.empty): Help = def apply(briefHelp: (String, String), detailedHelp: Map[String, String] = Map.empty): Help =
apply(briefHelp :: Nil, detailedHelp) apply(briefHelp :: Nil, detailedHelp)
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help = def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help =
apply(briefHelp, detailedHelp, Set.empty[String]) apply(briefHelp, detailedHelp, Set.empty[String])
def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String], more: Set[String]): Help = def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String], more: Set[String]): Help =
new Help0(briefHelp, detailedHelp, more) new Help0(briefHelp, detailedHelp, more)
@ -138,15 +201,17 @@ object Help {
case Some(x) => detail(x, h.detail) case Some(x) => detail(x, h.detail)
case None => case None =>
val brief = aligned(" ", " ", h.brief).mkString("\n", "\n", "\n") val brief = aligned(" ", " ", h.brief).mkString("\n", "\n", "\n")
val more = h.more.toSeq.sorted val more = h.more
if (more.isEmpty) if (more.isEmpty)
brief brief
else else
brief + "\n" + moreMessage(more) brief + "\n" + moreMessage(more.toSeq.sorted)
} }
def moreMessage(more: Seq[String]): String = def moreMessage(more: Seq[String]): String =
more.mkString("More command help available using 'help <command>' for:\n ", ", ", "\n") more.mkString("More command help available using 'help <command>' for:\n ", ", ", "\n")
} }
trait CommandDefinitions extends (State => State) { trait CommandDefinitions extends (State => State) {
def commands: Seq[Command] = ReflectUtilities.allVals[Command](this).values.toSeq def commands: Seq[Command] = ReflectUtilities.allVals[Command](this).values.toSeq
def apply(s: State): State = s ++ commands def apply(s: State): State = s ++ commands

View File

@ -10,9 +10,9 @@ import sbt.internal.util.complete.DefaultParsers._
import sbt.io.IO import sbt.io.IO
object CommandUtil { object CommandUtil {
def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine def readLines(files: Seq[File]): Seq[String] = files flatMap (IO.readLines(_)) flatMap processLine
def processLine(s: String) = { val trimmed = s.trim; if (ignoreLine(trimmed)) None else Some(trimmed) } def processLine(s: String): Option[String] = { val s2 = s.trim; if (ignoreLine(s2)) None else Some(s2) }
def ignoreLine(s: String) = s.isEmpty || s.startsWith("#") def ignoreLine(s: String): Boolean = s.isEmpty || s.startsWith("#")
private def canRead = (_: File).canRead private def canRead = (_: File).canRead
def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead def notReadable(files: Seq[File]): Seq[File] = files filterNot canRead
@ -20,17 +20,18 @@ object CommandUtil {
// slightly better fallback in case of older launcher // slightly better fallback in case of older launcher
def bootDirectory(state: State): File = def bootDirectory(state: State): File =
try { state.configuration.provider.scalaProvider.launcher.bootDirectory } try state.configuration.provider.scalaProvider.launcher.bootDirectory
catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile } catch { case _: NoSuchMethodError => new File(".").getAbsoluteFile }
def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] = if (in.isEmpty) Nil else { def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] = if (in.isEmpty) Nil else {
val width = in.map(_._1.length).max val width = in.iterator.map(_._1.length).max
in.map { case (a, b) => (pre + fill(a, width) + sep + b) } for ((a, b) <- in) yield pre + fill(a, width) + sep + b
} }
def fill(s: String, size: Int) = s + " " * math.max(size - s.length, 0)
def fill(s: String, size: Int): String = s + " " * math.max(size - s.length, 0)
def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State = def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State =
(s get key) match { s get key match {
case None => case None =>
s.log.error(ifMissing); s.fail s.log.error(ifMissing); s.fail
case Some(nav) => f(nav) case Some(nav) => f(nav)
@ -41,6 +42,7 @@ object CommandUtil {
val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString } val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString }
token(Space) ~> token(arg examples exampleStrings) token(Space) ~> token(arg examples exampleStrings)
} }
def detail(selected: String, detailMap: Map[String, String]): String = def detail(selected: String, detailMap: Map[String, String]): String =
detailMap.get(selected) match { detailMap.get(selected) match {
case Some(exactDetail) => exactDetail case Some(exactDetail) => exactDetail
@ -51,9 +53,11 @@ object CommandUtil {
else else
layoutDetails(details) layoutDetails(details)
} catch { } catch {
case pse: PatternSyntaxException => sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage) case pse: PatternSyntaxException =>
sys.error("Invalid regular expression (java.util.regex syntax).\n" + pse.getMessage)
} }
} }
def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] = def searchHelp(selected: String, detailMap: Map[String, String]): Map[String, String] =
{ {
val pattern = Pattern.compile(selected, HelpPatternFlags) val pattern = Pattern.compile(selected, HelpPatternFlags)
@ -69,9 +73,9 @@ object CommandUtil {
Nil Nil
} }
} }
def layoutDetails(details: Map[String, String]): String = def layoutDetails(details: Map[String, String]): String =
details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n") details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n")
final val HelpPatternFlags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE final val HelpPatternFlags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
} }

View File

@ -4,28 +4,14 @@
package sbt package sbt
import sbt.internal.{ import sbt.internal.{
Act, Act, Aggregation, BuildStructure, BuildUnit, CommandExchange, CommandStrings,
Aggregation, EvaluateConfigurations, Inspect, IvyConsole, Load, LoadedBuildUnit, Output,
BuildStructure, PluginsDebug, ProjectNavigation, Script, SessionSettings, SetResult,
BuildUnit, SettingCompletions, LogManager, DefaultBackgroundJobService
CommandExchange, }
CommandStrings, import sbt.internal.util.{
EvaluateConfigurations, AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types
Inspect,
IvyConsole,
Load,
LoadedBuildUnit,
Output,
PluginsDebug,
ProjectNavigation,
Script,
SessionSettings,
SetResult,
SettingCompletions,
LogManager,
DefaultBackgroundJobService
} }
import sbt.internal.util.{ AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types }
import sbt.util.{ Level, Logger } import sbt.util.{ Level, Logger }
import sbt.internal.util.complete.{ DefaultParsers, Parser } import sbt.internal.util.complete.{ DefaultParsers, Parser }
@ -124,6 +110,7 @@ final class ScriptMain extends xsbti.AppMain {
)) ))
} }
} }
final class ConsoleMain extends xsbti.AppMain { final class ConsoleMain extends xsbti.AppMain {
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
runManaged(initialState( runManaged(initialState(
@ -175,11 +162,17 @@ object BuiltinCommands {
def ConsoleCommands: Seq[Command] = Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop) def ConsoleCommands: Seq[Command] = Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)
def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, setLogLevel, early, act, nop) def ScriptCommands: Seq[Command] = Seq(ignore, exit, Script.command, setLogLevel, early, act, nop)
def DefaultCommands: Seq[Command] = Seq(ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject, templateCommand,
projects, project, reboot, read, history, set, sessionCommand, inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion, def DefaultCommands: Seq[Command] = Seq(
Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure, setLogLevel, plugin, plugins, ignore, help, completionsCommand, about, tasks, settingsCommand, loadProject,
ifLast, multi, shell, server, BasicCommands.client, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++ templateCommand, projects, project, reboot, read, history, set, sessionCommand,
compatCommands inspect, loadProjectImpl, loadFailed, Cross.crossBuild, Cross.switchVersion,
Cross.crossRestoreSession, setOnFailure, clearOnFailure, stashOnFailure, popOnFailure,
setLogLevel, plugin, plugins, ifLast, multi, shell, server, BasicCommands.client,
continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit,
early, initialize, act
) ++ compatCommands
def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil def DefaultBootCommands: Seq[String] = LoadProject :: (IfLast + " " + Shell) :: Nil
def boot = Command.make(BootCommand)(bootParser) def boot = Command.make(BootCommand)(bootParser)
@ -215,6 +208,7 @@ object BuiltinCommands {
val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct
if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "") if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "")
} }
def aboutScala(s: State, e: Extracted): String = def aboutScala(s: State, e: Extracted): String =
{ {
val scalaVersion = e.getOpt(Keys.scalaVersion) val scalaVersion = e.getOpt(Keys.scalaVersion)
@ -228,6 +222,7 @@ object BuiltinCommands {
case (None, None, None) => "" case (None, None, None) => ""
} }
} }
def aboutString(s: State): String = def aboutString(s: State): String =
{ {
val (name, ver, scalaVer, about) = (sbtName(s), sbtVersion(s), scalaVersion(s), aboutProject(s)) val (name, ver, scalaVer, about) = (sbtName(s), sbtVersion(s), scalaVersion(s), aboutProject(s))
@ -236,14 +231,22 @@ object BuiltinCommands {
|%s, %s plugins, and build definitions are using Scala %s |%s, %s plugins, and build definitions are using Scala %s
|""".stripMargin.format(name, ver, about, name, name, scalaVer) |""".stripMargin.format(name, ver, about, name, name, scalaVer)
} }
private[this] def selectScalaVersion(sv: Option[String], si: ScalaInstance): String = sv match { case Some(si.version) => si.version; case _ => si.actualVersion }
private[this] def selectScalaVersion(sv: Option[String], si: ScalaInstance): String =
sv match {
case Some(si.version) => si.version
case _ => si.actualVersion
}
private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch { case e: Exception => None } private[this] def quiet[T](t: => T): Option[T] = try { Some(t) } catch { case e: Exception => None }
def settingsCommand = showSettingLike(SettingsCommand, settingsPreamble, KeyRanks.MainSettingCutoff, key => !isTask(key.manifest)) def settingsCommand: Command =
showSettingLike(SettingsCommand, settingsPreamble, KeyRanks.MainSettingCutoff, key => !isTask(key.manifest))
def tasks = showSettingLike(TasksCommand, tasksPreamble, KeyRanks.MainTaskCutoff, key => isTask(key.manifest)) def tasks: Command =
showSettingLike(TasksCommand, tasksPreamble, KeyRanks.MainTaskCutoff, key => isTask(key.manifest))
def showSettingLike(command: String, preamble: String, cutoff: Int, keep: AttributeKey[_] => Boolean) = def showSettingLike(command: String, preamble: String, cutoff: Int, keep: AttributeKey[_] => Boolean): Command =
Command(command, settingsBrief(command), settingsDetailed(command))(showSettingParser(keep)) { Command(command, settingsBrief(command), settingsDetailed(command))(showSettingParser(keep)) {
case (s: State, (verbosity: Int, selected: Option[String])) => case (s: State, (verbosity: Int, selected: Option[String])) =>
if (selected.isEmpty) System.out.println(preamble) if (selected.isEmpty) System.out.println(preamble)
@ -304,20 +307,22 @@ object BuiltinCommands {
s.copy(definedCommands = DefaultCommands) s.copy(definedCommands = DefaultCommands)
} }
def initialize = Command.command(InitCommand) { s => def initialize: Command = Command.command(InitCommand) { s =>
/*"load-commands -base ~/.sbt/commands" :: */ readLines(readable(sbtRCs(s))).toList ::: s /*"load-commands -base ~/.sbt/commands" :: */ readLines(readable(sbtRCs(s))).toList ::: s
} }
def eval = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) { (s, arg) => def eval: Command = Command.single(EvalCommand, Help.more(EvalCommand, evalDetailed)) { (s, arg) =>
if (Project.isProjectLoaded(s)) loadedEval(s, arg) else rawEval(s, arg) if (Project.isProjectLoaded(s)) loadedEval(s, arg) else rawEval(s, arg)
s s
} }
private[this] def loadedEval(s: State, arg: String): Unit = { private[this] def loadedEval(s: State, arg: String): Unit = {
val extracted = Project extract s val extracted = Project extract s
import extracted._ import extracted._
val result = session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted)) val result = session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted))
s.log.info(s"ans: ${result.tpe} = ${result.getValue(currentLoader)}") s.log.info(s"ans: ${result.tpe} = ${result.getValue(currentLoader)}")
} }
private[this] def rawEval(s: State, arg: String): Unit = { private[this] def rawEval(s: State, arg: String): Unit = {
val app = s.configuration.provider val app = s.configuration.provider
val classpath = app.mainClasspath ++ app.scalaProvider.jars val classpath = app.mainClasspath ++ app.scalaProvider.jars
@ -325,15 +330,17 @@ object BuiltinCommands {
s.log.info(s"ans: ${result.tpe} = ${result.getValue(app.loader)}") s.log.info(s"ans: ${result.tpe} = ${result.getValue(app.loader)}")
} }
def sessionCommand: Command = Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command) def sessionCommand: Command =
Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
def reapply(newSession: SessionSettings, structure: BuildStructure, s: State): State = def reapply(newSession: SessionSettings, structure: BuildStructure, s: State): State =
{ {
s.log.info("Reapplying settings...") s.log.info("Reapplying settings...")
// Here, for correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings. // For correct behavior, we also need to re-inject a settings logger, as we'll be re-evaluating settings
val loggerInject = LogManager.settingsLogger(s) val loggerInject = LogManager.settingsLogger(s)
val withLogger = newSession.appendRaw(loggerInject :: Nil) val withLogger = newSession.appendRaw(loggerInject :: Nil)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(Project.showContextKey(newSession, structure)) val show = Project.showContextKey(newSession, structure)
val newStructure = Load.reapply(withLogger.mergeSettings, structure)(show)
Project.setProject(newSession, newStructure, s) Project.setProject(newSession, newStructure, s)
} }
@ -354,7 +361,9 @@ object BuiltinCommands {
arg, arg,
LineRange(0, 0) LineRange(0, 0)
)(cl) )(cl)
val setResult = if (all) SettingCompletions.setAll(extracted, settings) else SettingCompletions.setThis(s, extracted, settings, arg) val setResult =
if (all) SettingCompletions.setAll(extracted, settings)
else SettingCompletions.setThis(s, extracted, settings, arg)
s.log.info(setResult.quietSummary) s.log.info(setResult.quietSummary)
s.log.debug(setResult.verboseSummary) s.log.debug(setResult.verboseSummary)
reapply(setResult.session, structure, s) reapply(setResult.session, structure, s)
@ -369,7 +378,7 @@ object BuiltinCommands {
s s
} }
def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { def lastGrep: Command = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) {
case (s, (pattern, Some(sks))) => case (s, (pattern, Some(sks))) =>
val (str, _, display) = extractLast(s) val (str, _, display) = extractLast(s)
Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display) Output.lastGrep(sks, str.streams(s), pattern, printLast(s))(display)
@ -469,32 +478,38 @@ object BuiltinCommands {
for (id <- build.defined.keys.toSeq.sorted) log.info("\t" + prefix(id) + id) for (id <- build.defined.keys.toSeq.sorted) log.info("\t" + prefix(id) + id)
} }
def act = Command.customHelp(Act.actParser, actHelp) def act: Command = Command.customHelp(Act.actParser, actHelp)
def actHelp = (s: State) => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s) def actHelp: State => Help = s => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s)
def keysHelp(s: State): Help = def keysHelp(s: State): Help =
if (Project.isProjectLoaded(s)) if (Project.isProjectLoaded(s))
Help.detailOnly(taskDetail(allTaskAndSettingKeys(s))) Help.detailOnly(taskDetail(allTaskAndSettingKeys(s)))
else else
Help.empty Help.empty
def plugins = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s =>
def plugins: Command = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s =>
val helpString = PluginsDebug.helpAll(s) val helpString = PluginsDebug.helpAll(s)
System.out.println(helpString) System.out.println(helpString)
s s
} }
val pluginParser: State => Parser[AutoPlugin] = s => { val pluginParser: State => Parser[AutoPlugin] = s => {
val autoPlugins: Map[String, AutoPlugin] = PluginsDebug.autoPluginMap(s) val autoPlugins: Map[String, AutoPlugin] = PluginsDebug.autoPluginMap(s)
token(Space) ~> Act.knownPluginParser(autoPlugins, "plugin") token(Space) ~> Act.knownPluginParser(autoPlugins, "plugin")
} }
def plugin = Command(PluginCommand)(pluginParser) { (s, plugin) =>
def plugin: Command = Command(PluginCommand)(pluginParser) { (s, plugin) =>
val helpString = PluginsDebug.help(plugin, s) val helpString = PluginsDebug.help(plugin, s)
System.out.println(helpString) System.out.println(helpString)
s s
} }
def projects = Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) { def projects: Command =
case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds) Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) {
case (s, None) => showProjects(s); s case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds)
} case (s, None) => showProjects(s); s
}
def showProjects(s: State): Unit = { def showProjects(s: State): Unit = {
val extracted = Project extract s val extracted = Project extract s
import extracted._ import extracted._
@ -502,6 +517,7 @@ object BuiltinCommands {
listBuild(curi, structure.units(curi), true, cid, s.log) listBuild(curi, structure.units(curi), true, cid, s.log)
for ((uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, s.log) for ((uri, build) <- structure.units if curi != uri) listBuild(uri, build, false, cid, s.log)
} }
def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State = def transformExtraBuilds(s: State, f: List[URI] => List[URI]): State =
{ {
val original = Project.extraBuilds(s) val original = Project.extraBuilds(s)
@ -553,11 +569,11 @@ object BuiltinCommands {
Nil Nil
def loadProject: Command = def loadProject: Command =
Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser) { (s, arg) => Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)((s, arg) =>
loadProjectCommands(arg) ::: s loadProjectCommands(arg) ::: s
} )
private[this] def loadProjectParser = (s: State) => matched(Project.loadActionParser) private[this] def loadProjectParser: State => Parser[String] = _ => matched(Project.loadActionParser)
private[this] def loadProjectCommand(command: String, arg: String): String = s"$command $arg".trim private[this] def loadProjectCommand(command: String, arg: String): String = s"$command $arg".trim
def loadProjectImpl: Command = Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject) def loadProjectImpl: Command = Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject)

View File

@ -361,8 +361,10 @@ object Project extends ProjectExtra {
def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] = def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] =
ref match { case pr: ProjectRef => getProject(pr, structure); case _ => None } ref match { case pr: ProjectRef => getProject(pr, structure); case _ => None }
def getProject(ref: ProjectRef, structure: BuildStructure): Option[ResolvedProject] = getProject(ref, structure.units) def getProject(ref: ProjectRef, structure: BuildStructure): Option[ResolvedProject] = getProject(ref, structure.units)
def getProject(ref: ProjectRef, structure: LoadedBuild): Option[ResolvedProject] = getProject(ref, structure.units) def getProject(ref: ProjectRef, structure: LoadedBuild): Option[ResolvedProject] = getProject(ref, structure.units)
def getProject(ref: ProjectRef, units: Map[URI, LoadedBuildUnit]): Option[ResolvedProject] = def getProject(ref: ProjectRef, units: Map[URI, LoadedBuildUnit]): Option[ResolvedProject] =
(units get ref.build).flatMap(_.defined get ref.project) (units get ref.build).flatMap(_.defined get ref.project)
@ -371,6 +373,7 @@ object Project extends ProjectExtra {
val previousOnUnload = orIdentity(s get Keys.onUnload.key) val previousOnUnload = orIdentity(s get Keys.onUnload.key)
previousOnUnload(s.runExitHooks()) previousOnUnload(s.runExitHooks())
} }
def setProject(session: SessionSettings, structure: BuildStructure, s: State): State = def setProject(session: SessionSettings, structure: BuildStructure, s: State): State =
{ {
val unloaded = runUnloadHooks(s) val unloaded = runUnloadHooks(s)
@ -386,6 +389,7 @@ object Project extends ProjectExtra {
def getHooks(data: Settings[Scope]): (State => State, State => State) = (getHook(Keys.onLoad, data), getHook(Keys.onUnload, data)) def getHooks(data: Settings[Scope]): (State => State, State => State) = (getHook(Keys.onLoad, data), getHook(Keys.onUnload, data))
def current(state: State): ProjectRef = session(state).current def current(state: State): ProjectRef = session(state).current
def updateCurrent(s: State): State = def updateCurrent(s: State): State =
{ {
val structure = Project.structure(s) val structure = Project.structure(s)
@ -410,6 +414,7 @@ object Project extends ProjectExtra {
.put(templateResolverInfos.key, trs) .put(templateResolverInfos.key, trs)
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands) s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
} }
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap = def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap =
vopt match { case Some(v) => attributes.put(key, v); case None => attributes.remove(key) } vopt match { case Some(v) => attributes.put(key, v); case None => attributes.remove(key) }
@ -528,20 +533,21 @@ object Project extends ProjectExtra {
def relation(structure: BuildStructure, actual: Boolean)(implicit display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] = def relation(structure: BuildStructure, actual: Boolean)(implicit display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] =
relation(structure.settings, actual)(structure.delegates, structure.scopeLocal, display) relation(structure.settings, actual)(structure.delegates, structure.scopeLocal, display)
private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)(implicit delegates: Scope => Seq[Scope], scopeLocal: Def.ScopeLocal, display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] = private[sbt] def relation(settings: Seq[Def.Setting[_]], actual: Boolean)(
implicit delegates: Scope => Seq[Scope], scopeLocal: Def.ScopeLocal, display: Show[ScopedKey[_]]
): Relation[ScopedKey[_], ScopedKey[_]] =
{ {
type Rel = Relation[ScopedKey[_], ScopedKey[_]]
val cMap = Def.flattenLocals(Def.compiled(settings, actual)) val cMap = Def.flattenLocals(Def.compiled(settings, actual))
((Relation.empty: Rel) /: cMap) { val emptyRelation = Relation.empty[ScopedKey[_], ScopedKey[_]]
case (r, (key, value)) => (emptyRelation /: cMap) { case (r, (key, value)) => r + (key, value.dependencies) }
r + (key, value.dependencies)
}
} }
def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])(implicit display: Show[ScopedKey[_]]): String = def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])(implicit display: Show[ScopedKey[_]]): String =
showKeys(defs.map(scope => ScopedKey(scope, key))) showKeys(defs.map(scope => ScopedKey(scope, key)))
def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String = def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
showKeys(defs) showKeys(defs)
private[this] def showKeys(s: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String = private[this] def showKeys(s: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String =
s.map(display.show).sorted.mkString("\n\t", "\n\t", "\n\n") s.map(display.show).sorted.mkString("\n\t", "\n\t", "\n\n")
@ -575,20 +581,24 @@ object Project extends ProjectExtra {
def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn) def projectReturn(s: State): List[File] = getOrNil(s, ProjectReturn)
def inPluginProject(s: State): Boolean = projectReturn(s).length > 1 def inPluginProject(s: State): Boolean = projectReturn(s).length > 1
def setProjectReturn(s: State, pr: List[File]): State = s.copy(attributes = s.attributes.put(ProjectReturn, pr)) def setProjectReturn(s: State, pr: List[File]): State = s.copy(attributes = s.attributes.put(ProjectReturn, pr))
def loadAction(s: State, action: LoadAction.Value) = action match {
def loadAction(s: State, action: LoadAction.Value): (State, File) = action match {
case Return => case Return =>
projectReturn(s) match { projectReturn(s) match {
case current :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo) case _ /* current */ :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo)
case _ => sys.error("Not currently in a plugin definition") case _ => sys.error("Not currently in a plugin definition")
} }
case Current => case Current =>
val base = s.configuration.baseDirectory val base = s.configuration.baseDirectory
projectReturn(s) match { case Nil => (setProjectReturn(s, base :: Nil), base); case x :: xs => (s, x) } projectReturn(s) match { case Nil => (setProjectReturn(s, base :: Nil), base); case x :: xs => (s, x) }
case Plugins => case Plugins =>
val (newBase, oldStack) = if (Project.isProjectLoaded(s)) val (newBase, oldStack) =
(Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s)) if (Project.isProjectLoaded(s))
else // support changing to the definition project if it fails to load (Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s))
(BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil) else // support changing to the definition project if it fails to load
(BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil)
val newS = setProjectReturn(s, newBase :: oldStack) val newS = setProjectReturn(s, newBase :: oldStack)
(newS, newBase) (newS, newBase)
} }
@ -603,6 +613,7 @@ object Project extends ProjectExtra {
val mfi = EvaluateTask.minForcegcInterval(extracted, extracted.structure) val mfi = EvaluateTask.minForcegcInterval(extracted, extracted.structure)
runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch, fgc, mfi)) runTask(taskKey, state, EvaluateTaskConfig(r, checkCycles, p, ch, fgc, mfi))
} }
def runTask[T](taskKey: ScopedKey[Task[T]], state: State, config: EvaluateTaskConfig): Option[(State, Result[T])] = { def runTask[T](taskKey: ScopedKey[Task[T]], state: State, config: EvaluateTaskConfig): Option[(State, Result[T])] = {
val extracted = Project.extract(state) val extracted = Project.extract(state)
EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config) EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config)

View File

@ -2,47 +2,43 @@ package sbt
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.io.File import java.io.File
import sbt.io._, syntax._
import sbt.util._ import sbt.util._
import sbt.internal.util._ import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._
import xsbti.AppConfiguration import xsbti.AppConfiguration
import sbt.internal.inc.classpath.ClasspathUtilities
import BasicCommandStrings._
import BasicKeys._
import complete.DefaultParsers
import DefaultParsers._
import Command.applyEffect
import sbt.io._
import sbt.io.syntax._
import sbt.librarymanagement._ import sbt.librarymanagement._
import sbt.internal.librarymanagement.IvyConfiguration import sbt.internal.librarymanagement.IvyConfiguration
import sbt.internal.inc.classpath.ClasspathUtilities
import BasicCommandStrings._, BasicKeys._, Command.applyEffect
private[sbt] object TemplateCommandUtil { private[sbt] object TemplateCommandUtil {
def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser) def templateCommand: Command =
def templateCommandParser(state: State) = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)
{
val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map { case _ => Nil })
val infos = (state get templateResolverInfos) match {
case Some(infos) => infos.toList
case None => Nil
}
val log = state.globalLogging.full
val extracted = (Project extract state)
val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state)
val globalBase = BuildPaths.getGlobalBase(state)
val ivyScala = extracted.get(Keys.ivyScala in Keys.updateSbtClassifiers)
applyEffect(p)({ inputArg =>
val arguments = inputArg.toList ++
(state.remainingCommands.toList match {
case exec :: Nil if exec.commandLine == "shell" => Nil
case xs => xs map { _.commandLine }
})
run(infos, arguments, state.configuration, ivyConf, globalBase, ivyScala, log)
"exit" :: s2.copy(remainingCommands = Nil)
})
}
private def run(infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration, def templateCommandParser(state: State): Parser[() => State] = {
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): Unit = val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil))
val infos = (state get templateResolverInfos getOrElse Nil).toList
val log = state.globalLogging.full
val extracted = (Project extract state)
val (s2, ivyConf) = extracted.runTask(Keys.ivyConfiguration, state)
val globalBase = BuildPaths.getGlobalBase(state)
val ivyScala = extracted.get(Keys.ivyScala in Keys.updateSbtClassifiers)
applyEffect(p)({ inputArg =>
val arguments = inputArg.toList ++
(state.remainingCommands match {
case exec :: Nil if exec.commandLine == "shell" => Nil
case xs => xs map (_.commandLine)
})
run(infos, arguments, state.configuration, ivyConf, globalBase, ivyScala, log)
"exit" :: s2.copy(remainingCommands = Nil)
})
}
private def run(
infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger
): Unit =
infos find { info => infos find { info =>
val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log) val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log)
val hit = tryTemplate(info, arguments, loader) val hit = tryTemplate(info, arguments, loader)
@ -54,6 +50,7 @@ private[sbt] object TemplateCommandUtil {
case Some(_) => // do nothing case Some(_) => // do nothing
case None => System.err.println("Template not found for: " + arguments.mkString(" ")) case None => System.err.println("Template not found for: " + arguments.mkString(" "))
} }
private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean = private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean =
{ {
val resultObj = call(info.implementationClass, "isDefined", loader)( val resultObj = call(info.implementationClass, "isDefined", loader)(
@ -61,12 +58,21 @@ private[sbt] object TemplateCommandUtil {
)(arguments.toArray) )(arguments.toArray)
resultObj.asInstanceOf[Boolean] resultObj.asInstanceOf[Boolean]
} }
private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit = private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit =
call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray) call(info.implementationClass, "run", loader)(classOf[Array[String]])(arguments.toArray)
private def infoLoader(info: TemplateResolverInfo, config: AppConfiguration,
ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): ClassLoader = private def infoLoader(
ClasspathUtilities.toLoader(classpathForInfo(info, ivyConf, globalBase, ivyScala, log), config.provider.loader) info: TemplateResolverInfo, config: AppConfiguration, ivyConf: IvyConfiguration, globalBase: File,
private def call(interfaceClassName: String, methodName: String, loader: ClassLoader)(argTypes: Class[_]*)(args: AnyRef*): AnyRef = ivyScala: Option[IvyScala], log: Logger
): ClassLoader = {
val cp = classpathForInfo(info, ivyConf, globalBase, ivyScala, log)
ClasspathUtilities.toLoader(cp, config.provider.loader)
}
private def call(
interfaceClassName: String, methodName: String, loader: ClassLoader
)(argTypes: Class[_]*)(args: AnyRef*): AnyRef =
{ {
val interfaceClass = getInterfaceClass(interfaceClassName, loader) val interfaceClass = getInterfaceClass(interfaceClassName, loader)
val interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef] val interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef]
@ -76,10 +82,14 @@ private[sbt] object TemplateCommandUtil {
case e: InvocationTargetException => throw e.getCause case e: InvocationTargetException => throw e.getCause
} }
} }
private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader) private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader)
// Cache files under ~/.sbt/0.13/templates/org_name_version // Cache files under ~/.sbt/0.13/templates/org_name_version
private def classpathForInfo(info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): List[File] = private def classpathForInfo(
info: TemplateResolverInfo, ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala],
log: Logger
): List[File] =
{ {
val lm = new DefaultLibraryManagement(ivyConf, log) val lm = new DefaultLibraryManagement(ivyConf, log)
val templatesBaseDirectory = new File(globalBase, "templates") val templatesBaseDirectory = new File(globalBase, "templates")

View File

@ -18,9 +18,9 @@ object CommandStrings {
val BootCommand = "boot" val BootCommand = "boot"
val EvalCommand = "eval" val EvalCommand = "eval"
val evalBrief = (EvalCommand + " <expression>", "Evaluates a Scala expression and prints the result and type.") val evalBrief = (s"$EvalCommand <expression>", "Evaluates a Scala expression and prints the result and type.")
val evalDetailed = val evalDetailed =
EvalCommand + """ <expression> s"""$EvalCommand <expression>
Evaluates the given Scala expression and prints the result and type.""" Evaluates the given Scala expression and prints the result and type."""
@ -31,8 +31,8 @@ object CommandStrings {
s"""$multiTaskSyntax s"""$multiTaskSyntax
$multiTaskBrief""" $multiTaskBrief"""
def multiTaskSyntax = s"""$MultiTaskCommand <task>+""" def multiTaskSyntax = s"$MultiTaskCommand <task>+"
def multiTaskBrief = """Executes all of the specified tasks concurrently.""" def multiTaskBrief = "Executes all of the specified tasks concurrently."
def showHelp = Help(ShowCommand, (s"$ShowCommand <key>", showBrief), showDetailed) def showHelp = Help(ShowCommand, (s"$ShowCommand <key>", showBrief), showDetailed)
def showBrief = "Displays the result of evaluating the setting or task associated with 'key'." def showBrief = "Displays the result of evaluating the setting or task associated with 'key'."
@ -57,26 +57,26 @@ $ShowCommand <task>
val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.") val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.")
val lastGrepDetailed = val lastGrepDetailed =
LastGrepCommand + """ <pattern> s"""$LastGrepCommand <pattern>
Displays lines from the logging of previous commands that match `pattern`. Displays lines from the logging of previous commands that match `pattern`.
""" + LastGrepCommand + """ <pattern> [key] $LastGrepCommand <pattern> [key]
Displays lines from logging associated with `key` that match `pattern`. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task. Displays lines from logging associated with `key` that match `pattern`. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task.
<pattern> is a regular expression interpreted by java.util.Pattern. Matching text is highlighted (when highlighting is supported and enabled). <pattern> is a regular expression interpreted by java.util.Pattern. Matching text is highlighted (when highlighting is supported and enabled).
See also '""" + LastCommand + "'." See also '$LastCommand'."""
val lastBrief = (LastCommand, "Displays output from a previous command or the output from a specific task.") val lastBrief = (LastCommand, "Displays output from a previous command or the output from a specific task.")
val lastDetailed = val lastDetailed =
LastCommand + """ s"""$LastCommand
Prints the logging for the previous command, typically at a more verbose level. Prints the logging for the previous command, typically at a more verbose level.
""" + LastCommand + """ <key> $LastCommand <key>
Prints the logging associated with the provided key. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task. Prints the logging associated with the provided key. The key typically refers to a task (for example, test:compile). The logging that is displayed is restricted to the logging for that particular task.
See also '""" + LastGrepCommand + "'." See also '$LastGrepCommand'."""
val exportBrief = (ExportCommand + " <tasks>+", "Executes tasks and displays the equivalent command lines.") val exportBrief = (s"$ExportCommand <tasks>+", "Executes tasks and displays the equivalent command lines.")
val exportDetailed = val exportDetailed =
s"""$ExportCommand [--last] <task>+ s"""$ExportCommand [--last] <task>+
Runs the specified tasks and prints the equivalent command lines or other exportable information for those runs. Runs the specified tasks and prints the equivalent command lines or other exportable information for those runs.
@ -92,7 +92,7 @@ $ShowCommand <task>
""" """
val InspectCommand = "inspect" val InspectCommand = "inspect"
val inspectBrief = (InspectCommand + " [uses|tree|definitions] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.") val inspectBrief = (s"$InspectCommand [uses|tree|definitions] <key>", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.")
val inspectDetailed = s""" val inspectDetailed = s"""
|$InspectCommand <key> |$InspectCommand <key>
| |
@ -133,7 +133,7 @@ $ShowCommand <task>
val SetCommand = "set" val SetCommand = "set"
val setBrief = (s"$SetCommand [every] <setting>", "Evaluates a Setting and applies it to the current project.") val setBrief = (s"$SetCommand [every] <setting>", "Evaluates a Setting and applies it to the current project.")
val setDetailed = val setDetailed =
SetCommand + """ [every] <setting-expression> s"""$SetCommand [every] <setting-expression>
Applies the given setting to the current project: Applies the given setting to the current project:
1) Constructs the expression provided as an argument by compiling and loading it. 1) Constructs the expression provided as an argument by compiling and loading it.
@ -150,68 +150,82 @@ $ShowCommand <task>
""" """
def SessionCommand = "session" def SessionCommand = "session"
def sessionBrief = (SessionCommand, "Manipulates session settings. For details, run 'help " + SessionCommand + "'.")
def sessionBrief =
(SessionCommand, s"Manipulates session settings. For details, run 'help $SessionCommand'.")
def settingsPreamble = commonPreamble("settings") def settingsPreamble = commonPreamble("settings")
def tasksPreamble = commonPreamble("tasks") + """ def tasksPreamble = commonPreamble("tasks") + """
Tasks produce values. Use the 'show' command to run the task and print the resulting value.""" Tasks produce values. Use the 'show' command to run the task and print the resulting value."""
def commonPreamble(label: String) = """ def commonPreamble(label: String) = s"""
This is a list of %s defined for the current project. This is a list of $label defined for the current project.
It does not list the scopes the %<s are defined in; use the 'inspect' command for that.""".format(label) It does not list the scopes the $label are defined in; use the 'inspect' command for that."""
def settingsBrief(label: String) = (label, s"Lists the $label defined for the current project.")
import BasicCommandStrings.HelpCommand
def settingsBrief(label: String) = (label, "Lists the " + label + " defined for the current project.")
def settingsDetailed(label: String) = def settingsDetailed(label: String) =
""" s"""
Syntax summary Syntax summary
%s [-(v|-vv|...|-V)] [<filter>] $label [-(v|-vv|...|-V)] [<filter>]
%<s $label
Displays the main %<s defined directly or indirectly for the current project. Displays the main $label defined directly or indirectly for the current project.
-v -v
Displays additional %<s. More 'v's increase the number of %<s displayed. Displays additional $label. More 'v's increase the number of $label displayed.
-V -V
displays all %<s displays all $label
<filter> <filter>
Restricts the %<s that are displayed. The names of %<s are searched for an exact match against the filter, in which case only the description of the exact match is displayed. Otherwise, the filter is interpreted as a regular expression and all %<s whose name or description match the regular expression are displayed. Note that this is an additional filter on top of the %<s selected by the -v style switches, so you must specify -V to search all %<s. Use the %s command to search all commands, tasks, and settings at once. Restricts the $label that are displayed. The names of $label are searched for
""".format(label, BasicCommandStrings.HelpCommand) an exact match against the filter, in which case only the description of the
exact match is displayed. Otherwise, the filter is interpreted as a regular
expression and all $label whose name or description match the regular
expression are displayed. Note that this is an additional filter on top of
the $label selected by the -v style switches, so you must specify -V to search
all $label. Use the $HelpCommand command to search all commands, tasks, and
settings at once.
"""
def moreAvailableMessage(label: String, search: Boolean) = def moreAvailableMessage(label: String, search: Boolean) = {
"More %s may be %s by increasing verbosity. See '%s %s'.\n".format(label, if (search) "searched" else "viewed", BasicCommandStrings.HelpCommand, label) val verb = if (search) "searched" else "viewed"
s"More $label may be $verb by increasing verbosity. See '$HelpCommand $label'\n"
}
def aboutBrief = "Displays basic information about sbt and the build." def aboutBrief = "Displays basic information about sbt and the build."
def aboutDetailed = aboutBrief def aboutDetailed = aboutBrief
def projectBrief = (ProjectCommand, "Displays the current project or changes to the provided `project`.") def projectBrief = (ProjectCommand, "Displays the current project or changes to the provided `project`.")
def projectDetailed = def projectDetailed =
ProjectCommand + s"""$ProjectCommand
"""
Displays the name of the current project. Displays the name of the current project.
""" + ProjectCommand + """ name $ProjectCommand name
Changes to the project with the provided name. Changes to the project with the provided name.
This command fails if there is no project with the given name. This command fails if there is no project with the given name.
""" + ProjectCommand + """ {uri} $ProjectCommand {uri}
Changes to the root project in the build defined by `uri`. Changes to the root project in the build defined by `uri`.
`uri` must have already been declared as part of the build, such as with Project.dependsOn. `uri` must have already been declared as part of the build, such as with Project.dependsOn.
""" + ProjectCommand + """ {uri}name $ProjectCommand {uri}name
Changes to the project `name` in the build defined by `uri`. Changes to the project `name` in the build defined by `uri`.
`uri` must have already been declared as part of the build, such as with Project.dependsOn. `uri` must have already been declared as part of the build, such as with Project.dependsOn.
""" + ProjectCommand + """ / $ProjectCommand /
Changes to the initial project. Changes to the initial project.
""" + ProjectCommand + """ .. $ProjectCommand ..
Changes to the parent project of the current project. Changes to the parent project of the current project.
If there is no parent project, the current project is unchanged. If there is no parent project, the current project is unchanged.
@ -221,15 +235,15 @@ Syntax summary
def projectsBrief = "Lists the names of available projects or temporarily adds/removes extra builds to the session." def projectsBrief = "Lists the names of available projects or temporarily adds/removes extra builds to the session."
def projectsDetailed = def projectsDetailed =
ProjectsCommand + """ s"""$ProjectsCommand
List the names of available builds and the projects defined in those builds. List the names of available builds and the projects defined in those builds.
""" + ProjectsCommand + """ add <URI>+ $ProjectsCommand add <URI>+
Adds the builds at the provided URIs to this session. Adds the builds at the provided URIs to this session.
These builds may be selected using the """ + ProjectCommand + """ command. These builds may be selected using the sProjectCommand command.
Alternatively, tasks from these builds may be run using the explicit syntax {URI}project/task Alternatively, tasks from these builds may be run using the explicit syntax {URI}project/task
""" + ProjectsCommand + """ remove <URI>+ $ProjectsCommand remove <URI>+
Removes extra builds from this session. Removes extra builds from this session.
Builds explicitly listed in the build definition are not affected by this command. Builds explicitly listed in the build definition are not affected by this command.
""" """
@ -250,8 +264,8 @@ Syntax summary
def LoadProjectImpl = "loadp" def LoadProjectImpl = "loadp"
def LoadProject = "reload" def LoadProject = "reload"
def LoadProjectBrief = (LoadProject, "(Re)loads the current project or changes to plugins project or returns from it.") def LoadProjectBrief = (LoadProject, "(Re)loads the current project or changes to plugins project or returns from it.")
def LoadProjectDetailed = LoadProject + def LoadProjectDetailed =
s""" s"""$LoadProject
\t(Re)loads the project in the current directory. \t(Re)loads the project in the current directory.
@ -266,7 +280,7 @@ $LoadProject return
def InitCommand = "initialize" def InitCommand = "initialize"
def InitBrief = (InitCommand, "Initializes command processing.") def InitBrief = (InitCommand, "Initializes command processing.")
def InitDetailed = def InitDetailed =
InitCommand + """ s"""$InitCommand
Initializes command processing. Initializes command processing.
Runs the following commands. Runs the following commands.

View File

@ -242,16 +242,26 @@ private[sbt] object Load {
} }
// Reevaluates settings after modifying them. Does not recompile or reload any build components. // Reevaluates settings after modifying them. Does not recompile or reload any build components.
def reapply(newSettings: Seq[Setting[_]], structure: BuildStructure)(implicit display: Show[ScopedKey[_]]): BuildStructure = def reapply(
newSettings: Seq[Setting[_]], structure: BuildStructure
)(implicit display: Show[ScopedKey[_]]): BuildStructure =
{ {
val transformed = finalTransforms(newSettings) val transformed = finalTransforms(newSettings)
val newData = Def.make(transformed)(structure.delegates, structure.scopeLocal, display) val newData = Def.make(transformed)(structure.delegates, structure.scopeLocal, display)
val newIndex = structureIndex(newData, transformed, index => BuildUtil(structure.root, structure.units, index, newData), structure.units) def extra(index: KeyIndex) = BuildUtil(structure.root, structure.units, index, newData)
val newIndex = structureIndex(newData, transformed, extra, structure.units)
val newStreams = mkStreams(structure.units, structure.root, newData) val newStreams = mkStreams(structure.units, structure.root, newData)
new BuildStructure(units = structure.units, root = structure.root, settings = transformed, data = newData, index = newIndex, streams = newStreams, delegates = structure.delegates, scopeLocal = structure.scopeLocal) new BuildStructure(
units = structure.units, root = structure.root, settings = transformed, data = newData,
index = newIndex, streams = newStreams, delegates = structure.delegates,
scopeLocal = structure.scopeLocal)
} }
def isProjectThis(s: Setting[_]) = s.key.scope.project match { case This | Select(ThisProject) => true; case _ => false } def isProjectThis(s: Setting[_]): Boolean =
s.key.scope.project match {
case This | Select(ThisProject) => true
case _ => false
}
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: InjectSettings): Seq[Setting[_]] = def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: InjectSettings): Seq[Setting[_]] =
{ {
@ -291,7 +301,8 @@ private[sbt] object Load {
() => eval () => eval
} }
def mkEval(unit: BuildUnit): Eval = mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions) def mkEval(unit: BuildUnit): Eval =
mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions)
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval = def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
mkEval(defs.target ++ plugs.classpath, defs.base, options) mkEval(defs.target ++ plugs.classpath, defs.base, options)
@ -335,7 +346,8 @@ private[sbt] object Load {
BuildLoader(components, fail, s, config) BuildLoader(components, fail, s, config)
} }
def load(file: File, loaders: BuildLoader, extra: List[URI]): PartBuild = loadURI(IO.directoryURI(file), loaders, extra) def load(file: File, loaders: BuildLoader, extra: List[URI]): PartBuild =
loadURI(IO.directoryURI(file), loaders, extra)
def loadURI(uri: URI, loaders: BuildLoader, extra: List[URI]): PartBuild = def loadURI(uri: URI, loaders: BuildLoader, extra: List[URI]): PartBuild =
{ {

View File

@ -9,7 +9,11 @@ import Plugins._
import PluginsDebug._ import PluginsDebug._
import java.net.URI import java.net.URI
private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: Map[String, AttributeKey[_]], val provided: Relation[AutoPlugin, AttributeKey[_]]) { private[sbt] class PluginsDebug(
val available: List[AutoPlugin],
val nameToKey: Map[String, AttributeKey[_]],
val provided: Relation[AutoPlugin, AttributeKey[_]]
) {
/** /**
* The set of [[AutoPlugin]]s that might define a key named `keyName`. * The set of [[AutoPlugin]]s that might define a key named `keyName`.
* Because plugins can define keys in different scopes, this should only be used as a guideline. * Because plugins can define keys in different scopes, this should only be used as a guideline.
@ -18,20 +22,23 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
case None => Set.empty case None => Set.empty
case Some(key) => provided.reverse(key) case Some(key) => provided.reverse(key)
} }
/** Describes alternative approaches for defining key [[keyName]] in [[context]].*/
/** Describes alternative approaches for defining key `keyName` in [[Context]]. */
def toEnable(keyName: String, context: Context): List[PluginEnable] = def toEnable(keyName: String, context: Context): List[PluginEnable] =
providers(keyName).toList.map(plugin => pluginEnable(context, plugin)) providers(keyName).toList.map(plugin => pluginEnable(context, plugin))
/** Provides text to suggest how [[notFoundKey]] can be defined in [[context]]. */ /** Provides text to suggest how `notFoundKey` can be defined in [[Context]]. */
def debug(notFoundKey: String, context: Context): String = def debug(notFoundKey: String, context: Context): String =
{ {
val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) { val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) {
case pa: PluginActivated => Left(pa) case pa: PluginActivated => Left(pa)
case pd: EnableDeactivated => Right(pd) case pd: EnableDeactivated => Right(pd)
} }
val activePrefix = if (activated.nonEmpty) s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n" else "" val activePrefix = if (activated.isEmpty) "" else
s"Some already activated plugins define $notFoundKey: ${activated.mkString(", ")}\n"
activePrefix + debugDeactivated(notFoundKey, deactivated) activePrefix + debugDeactivated(notFoundKey, deactivated)
} }
private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String = private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String =
{ {
val (impossible, possible) = Util.separate(deactivated) { val (impossible, possible) = Util.separate(deactivated) {
@ -41,23 +48,27 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
if (possible.nonEmpty) { if (possible.nonEmpty) {
val explained = possible.map(explainPluginEnable) val explained = possible.map(explainPluginEnable)
val possibleString = val possibleString =
if (explained.size > 1) explained.zipWithIndex.map { case (s, i) => s"$i. $s" }.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "") if (explained.size > 1) explained.zipWithIndex.map { case (s, i) => s"$i. $s" }
.mkString(s"Multiple plugins are available that can provide $notFoundKey:\n", "\n", "")
else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}" else s"$notFoundKey is provided by an available (but not activated) plugin:\n${explained.mkString}"
def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ") def impossiblePlugins = impossible.map(_.plugin.label).mkString(", ")
val imPostfix = if (impossible.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are impossible to add: $impossiblePlugins" val imPostfix = if (impossible
.isEmpty) "" else s"\n\nThere are other available plugins that provide $notFoundKey, but they are " +
s"impossible to add: $impossiblePlugins"
possibleString + imPostfix possibleString + imPostfix
} else if (impossible.isEmpty) } else if (impossible.isEmpty)
s"No available plugin provides key $notFoundKey." s"No available plugin provides key $notFoundKey."
else { else {
val explanations = impossible.map(explainPluginEnable) val explanations = impossible.map(explainPluginEnable)
explanations.mkString(s"Plugins are available that could provide $notFoundKey, but they are impossible to add:\n\t", "\n\t", "") val preamble = s"Plugins are available that could provide $notFoundKey"
explanations.mkString(s"$preamble, but they are impossible to add:\n\t", "\n\t", "")
} }
} }
/** Text that suggests how to activate [[plugin]] in [[context]] if possible and if it is not already activated.*/ /** Text that suggests how to activate [[AutoPlugin]] in [[Context]] if possible and if it is not already activated. */
def help(plugin: AutoPlugin, context: Context): String = def help(plugin: AutoPlugin, context: Context): String =
if (context.enabled.contains(plugin)) activatedHelp(plugin) if (context.enabled.contains(plugin)) activatedHelp(plugin) else deactivatedHelp(plugin, context)
else deactivatedHelp(plugin, context)
private def activatedHelp(plugin: AutoPlugin): String = private def activatedHelp(plugin: AutoPlugin): String =
{ {
val prefix = s"${plugin.label} is activated." val prefix = s"${plugin.label} is activated."
@ -67,6 +78,7 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey:
val confsString = if (configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}" val confsString = if (configs.isEmpty) "" else s"\nIt defines these configurations: ${multi(configs.map(_.name))}"
prefix + keysString + confsString prefix + keysString + confsString
} }
private def deactivatedHelp(plugin: AutoPlugin, context: Context): String = private def deactivatedHelp(plugin: AutoPlugin, context: Context): String =
{ {
val prefix = s"${plugin.label} is NOT activated." val prefix = s"${plugin.label} is NOT activated."
@ -105,6 +117,7 @@ private[sbt] object PluginsDebug {
import extracted._ import extracted._
structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap
} }
private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] = private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] =
build.unit.plugins.detected.autoPlugins map { _.value } build.unit.plugins.detected.autoPlugins map { _.value }
@ -120,7 +133,10 @@ private[sbt] object PluginsDebug {
lazy val debug = PluginsDebug(context.available) lazy val debug = PluginsDebug(context.available)
if (!pluginsThisBuild.contains(plugin)) { if (!pluginsThisBuild.contains(plugin)) {
val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1) val availableInBuilds: List[URI] = perBuild.toList.filter(_._2(plugin)).map(_._1)
s"Plugin ${plugin.label} is only available in builds:\n\t${availableInBuilds.mkString("\n\t")}\nSwitch to a project in one of those builds using `project` and rerun this command for more information." val s1 = s"Plugin ${plugin.label} is only available in builds:"
val s2 = availableInBuilds.mkString("\n\t")
val s3 = s"Switch to a project in one of those builds using `project` and rerun this command for more information."
s"$s1\n\t$s2\n$s3"
} else if (definesPlugin(currentProject)) } else if (definesPlugin(currentProject))
debug.activatedHelp(plugin) debug.activatedHelp(plugin)
else { else {
@ -128,7 +144,8 @@ private[sbt] object PluginsDebug {
val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref))) val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref)))
if (definedInAggregated.nonEmpty) { if (definedInAggregated.nonEmpty) {
val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified val projectNames = definedInAggregated.map(_.project) // TODO: usually in this build, but could technically require the build to be qualified
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t${projectNames.mkString("\n\t")}" val s2 = projectNames.mkString("\n\t")
s"Plugin ${plugin.label} is not activated on this project, but this project aggregates projects where it is activated:\n\t$s2"
} else { } else {
val base = debug.deactivatedHelp(plugin, context) val base = debug.deactivatedHelp(plugin, context)
val aggNote = if (thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This" val aggNote = if (thisAggregated.nonEmpty) "Note: This project aggregates other projects and this" else "Note: This"
@ -139,7 +156,7 @@ private[sbt] object PluginsDebug {
} }
} }
/** Precomputes information for debugging plugins. */ /** Pre-computes information for debugging plugins. */
def apply(available: List[AutoPlugin]): PluginsDebug = def apply(available: List[AutoPlugin]): PluginsDebug =
{ {
val keyR = definedKeys(available) val keyR = definedKeys(available)
@ -154,36 +171,58 @@ private[sbt] object PluginsDebug {
* @param deducePlugin The function used to compute the model. * @param deducePlugin The function used to compute the model.
* @param available All [[AutoPlugin]]s available for consideration. * @param available All [[AutoPlugin]]s available for consideration.
*/ */
final case class Context(initial: Plugins, enabled: Seq[AutoPlugin], deducePlugin: (Plugins, Logger) => Seq[AutoPlugin], available: List[AutoPlugin], log: Logger) final case class Context(
initial: Plugins,
enabled: Seq[AutoPlugin],
deducePlugin: (Plugins, Logger) => Seq[AutoPlugin],
available: List[AutoPlugin],
log: Logger
)
/** Describes the steps to activate a plugin in some context. */ /** Describes the steps to activate a plugin in some context. */
sealed abstract class PluginEnable sealed abstract class PluginEnable
/** Describes a [[plugin]] that is already activated in the [[context]].*/ /** Describes a [[plugin]] that is already activated in the [[context]].*/
final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable
sealed abstract class EnableDeactivated extends PluginEnable sealed abstract class EnableDeactivated extends PluginEnable
/** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */ /** Describes a [[plugin]] that cannot be activated in a [[context]] due to [[contradictions]] in requirements. */
final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin]) extends EnableDeactivated final case class PluginImpossible(plugin: AutoPlugin, context: Context, contradictions: Set[AutoPlugin])
extends EnableDeactivated
/** /**
* Describes the requirements for activating [[plugin]] in [[context]]. * Describes the requirements for activating [[plugin]] in [[context]].
* @param context The base plugins, exclusions, and ultimately activated plugins * @param context The base plugins, exclusions, and ultimately activated plugins
* @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped * @param blockingExcludes Existing exclusions that prevent [[plugin]] from being activated and must be dropped
* @param enablingPlugins [[AutoPlugin]]s that are not currently enabled, but need to be enabled for [[plugin]] to activate * @param enablingPlugins [[AutoPlugin]]s that are not currently enabled,
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating, but are not required for [[plugin]] to activate * but need to be enabled for [[plugin]] to activate
* @param extraEnabledPlugins Plugins that will be enabled as a result of [[plugin]] activating,
* but are not required for [[plugin]] to activate
* @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating * @param willRemove Plugins that will be deactivated as a result of [[plugin]] activating
* @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate. These require an explicit exclusion or dropping a transitive [[AutoPlugin]]. * @param deactivate Describes plugins that must be deactivated for [[plugin]] to activate.
* These require an explicit exclusion or dropping a transitive [[AutoPlugin]].
*/ */
final case class PluginRequirements(plugin: AutoPlugin, context: Context, blockingExcludes: Set[AutoPlugin], enablingPlugins: Set[AutoPlugin], extraEnabledPlugins: Set[AutoPlugin], willRemove: Set[AutoPlugin], deactivate: List[DeactivatePlugin]) extends EnableDeactivated final case class PluginRequirements(
plugin: AutoPlugin,
context: Context,
blockingExcludes: Set[AutoPlugin],
enablingPlugins: Set[AutoPlugin],
extraEnabledPlugins: Set[AutoPlugin],
willRemove: Set[AutoPlugin],
deactivate: List[DeactivatePlugin]
) extends EnableDeactivated
/** /**
* Describes a [[plugin]] that must be removed in order to activate another plugin in some context. * Describes a [[plugin]] that must be removed in order to activate another plugin in some context.
* The [[plugin]] can always be directly, explicitly excluded. * The [[plugin]] can always be directly, explicitly excluded.
* @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without affecting the other plugin. If empty, a direct exclusion is required. * @param removeOneOf If non-empty, removing one of these [[AutoPlugin]]s will deactivate [[plugin]] without
* affecting the other plugin. If empty, a direct exclusion is required.
* @param newlySelected If false, this plugin was selected in the original context. * @param newlySelected If false, this plugin was selected in the original context.
*/ */
final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean) final case class DeactivatePlugin(plugin: AutoPlugin, removeOneOf: Set[AutoPlugin], newlySelected: Boolean)
/** Determines how to enable [[plugin]] in [[context]]. */ /** Determines how to enable [[AutoPlugin]] in [[Context]]. */
def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable = def pluginEnable(context: Context, plugin: AutoPlugin): PluginEnable =
if (context.enabled.contains(plugin)) if (context.enabled.contains(plugin))
PluginActivated(plugin, context) PluginActivated(plugin, context)
@ -237,12 +276,15 @@ private[sbt] object PluginsDebug {
// The model that results when the minimal plugins are enabled and the minimal plugins are excluded. // The model that results when the minimal plugins are enabled and the minimal plugins are excluded.
// This can include more plugins than just `minRequiredPlugins` because the plugins required for `plugin` // This can include more plugins than just `minRequiredPlugins` because the plugins required for `plugin`
// might activate other plugins as well. // might activate other plugins as well.
val incrementalInputs = and(includeAll(minRequiredPlugins ++ initialPlugins), excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins)) val incrementalInputs = and(
includeAll(minRequiredPlugins ++ initialPlugins),
excludeAll(minAbsentPlugins ++ initialExcludes -- minRequiredPlugins)
)
val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet val incrementalModel = context.deducePlugin(incrementalInputs, context.log).toSet
// Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`. // Plugins that are newly enabled as a result of selecting the plugins needed for `plugin`, but aren't strictly required for `plugin`.
// These could be excluded and `plugin` and the user's current plugins would still be activated. // These could be excluded and `plugin` and the user's current plugins would still be activated.
val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel val extraPlugins = incrementalModel -- minRequiredPlugins -- initialModel
// Plugins that will no longer be enabled as a result of enabling `plugin`. // Plugins that will no longer be enabled as a result of enabling `plugin`.
val willRemove = initialModel -- incrementalModel val willRemove = initialModel -- incrementalModel
@ -318,8 +360,10 @@ private[sbt] object PluginsDebug {
private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) = private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) =
s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded." s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded."
private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) = private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) =
s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}" s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}"
private[this] def transitiveString(transitive: Boolean) = private[this] def transitiveString(transitive: Boolean) =
if (transitive) "(transitive) " else "" if (transitive) "(transitive) " else ""
@ -328,6 +372,7 @@ private[sbt] object PluginsDebug {
private[this] def requiredPlugin(plugin: AutoPlugin) = private[this] def requiredPlugin(plugin: AutoPlugin) =
s"Required plugin ${plugin.label} not present." s"Required plugin ${plugin.label} not present."
private[this] def requiredPlugins(plugins: List[AutoPlugin]) = private[this] def requiredPlugins(plugins: List[AutoPlugin]) =
s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}" s"Required plugins not present:\n\t${plugins.map(_.label).mkString("\n\t")}"
@ -343,6 +388,7 @@ private[sbt] object PluginsDebug {
private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) = private[this] def willAddPlugin(base: AutoPlugin)(plugin: AutoPlugin) =
s"Enabling ${base.label} will also enable ${plugin.label}" s"Enabling ${base.label} will also enable ${plugin.label}"
private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}" s"Enabling ${base.label} will also enable:\n\t${labels(plugins).mkString("\n\t")}"
@ -351,6 +397,7 @@ private[sbt] object PluginsDebug {
private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) = private[this] def willRemovePlugin(base: AutoPlugin)(plugin: AutoPlugin) =
s"Enabling ${base.label} will disable ${plugin.label}" s"Enabling ${base.label} will disable ${plugin.label}"
private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) =
s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}" s"Enabling ${base.label} will disable:\n\t${labels(plugins).mkString("\n\t")}"
@ -359,10 +406,13 @@ private[sbt] object PluginsDebug {
private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String = private[this] def needToDeactivate(deactivate: List[DeactivatePlugin]): String =
str(deactivate)(deactivate1, deactivateN) str(deactivate)(deactivate1, deactivateN)
private[this] def deactivateN(plugins: List[DeactivatePlugin]): String = private[this] def deactivateN(plugins: List[DeactivatePlugin]): String =
plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "") plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "")
private[this] def deactivate1(deactivate: DeactivatePlugin): String = private[this] def deactivate1(deactivate: DeactivatePlugin): String =
s"Need to deactivate ${deactivateString(deactivate)}" s"Need to deactivate ${deactivateString(deactivate)}"
private[this] def deactivateString(d: DeactivatePlugin): String = private[this] def deactivateString(d: DeactivatePlugin): String =
{ {
val removePluginsString: String = val removePluginsString: String =
@ -377,8 +427,17 @@ private[sbt] object PluginsDebug {
private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String = private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String =
str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin)) str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin))
private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = {
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent. Please report the problem to the plugin's author." val s1 = s"There is no way to enable plugin ${plugin.label}."
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = val s2 = s"It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent."
s"There is no way to enable plugin ${plugin.label}. It (or its dependencies) requires these plugins to be both present and absent:\n\t${labels(contradictions).mkString("\n\t")}\nPlease report the problem to the plugin's author." val s3 = s"Please report the problem to the plugin's author."
s"$s1 $s2 $s3"
}
private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = {
val s1 = s"There is no way to enable plugin ${plugin.label}."
val s2 = s"It (or its dependencies) requires these plugins to be both present and absent:"
val s3 = s"Please report the problem to the plugin's author."
s"$s1 $s2:\n\t${labels(contradictions).mkString("\n\t")}\n$s3"
}
} }

View File

@ -4,29 +4,29 @@
package sbt package sbt
package internal package internal
import sbt.internal.util.complete
import Project.updateCurrent
import Keys.sessionSettings
import complete.{ DefaultParsers, Parser }
import DefaultParsers._
import java.net.URI import java.net.URI
import sbt.internal.util.complete, complete.{ DefaultParsers, Parser }, DefaultParsers._
import sbt.compiler.Eval
import Keys.sessionSettings
import Project.updateCurrent
object ProjectNavigation { object ProjectNavigation {
def command(s: State): Parser[() => State] = def command(s: State): Parser[() => State] =
if (s get sessionSettings isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command if (s get sessionSettings isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command
} }
final class ProjectNavigation(s: State) { final class ProjectNavigation(s: State) {
val extracted = Project extract s val extracted: Extracted = Project extract s
import extracted.{ currentRef, structure, session } import extracted.{ currentRef, structure, session }
def setProject(nuri: URI, nid: String) = def setProject(nuri: URI, nid: String): State =
{ {
val neval = if (currentRef.build == nuri) session.currentEval else mkEval(nuri) val neval = if (currentRef.build == nuri) session.currentEval else mkEval(nuri)
updateCurrent(s.put(sessionSettings, session.setCurrent(nuri, nid, neval))) updateCurrent(s.put(sessionSettings, session.setCurrent(nuri, nid, neval)))
} }
def mkEval(nuri: URI) = Load.lazyEval(structure.units(nuri).unit)
def getRoot(uri: URI) = Load.getRootProject(structure.units)(uri) def mkEval(nuri: URI): () => Eval = Load.lazyEval(structure.units(nuri).unit)
def getRoot(uri: URI): String = Load.getRootProject(structure.units)(uri)
def apply(action: Option[ResolvedReference]): State = def apply(action: Option[ResolvedReference]): State =
action match { action match {
@ -38,12 +38,13 @@ final class ProjectNavigation(s: State) {
if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined
} }
def show(): Unit = s.log.info(currentRef.project + " (in build " + currentRef.build + ")") def show(): Unit = s.log.info(s"${currentRef.project} (in build ${currentRef.build})")
def selectProject(uri: URI, to: String): State = def selectProject(uri: URI, to: String): State =
if (structure.units(uri).defined.contains(to)) if (structure.units(uri).defined.contains(to))
setProject(uri, to) setProject(uri, to)
else else
fail("Invalid project name '" + to + "' in build " + uri + " (type 'projects' to list available projects).") fail(s"Invalid project name '$to' in build $uri (type 'projects' to list available projects).")
def changeBuild(newBuild: URI): State = def changeBuild(newBuild: URI): State =
if (structure.units contains newBuild) if (structure.units contains newBuild)
@ -51,14 +52,9 @@ final class ProjectNavigation(s: State) {
else else
fail("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).") fail("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).")
def fail(msg: String): State = def fail(msg: String): State = { s.log.error(msg); s.fail }
{
s.log.error(msg)
s.fail
}
import complete.Parser._ import Parser._, complete.Parsers._
import complete.Parsers._
val parser: Parser[Option[ResolvedReference]] = val parser: Parser[Option[ResolvedReference]] =
{ {
@ -66,6 +62,8 @@ final class ProjectNavigation(s: State) {
val root = token('/' ^^^ rootRef) val root = token('/' ^^^ rootRef)
success(None) | some(token(Space) ~> (root | reference)) success(None) | some(token(Space) ~> (root | reference))
} }
def rootRef = ProjectRef(currentRef.build, getRoot(currentRef.build)) def rootRef = ProjectRef(currentRef.build, getRoot(currentRef.build))
val command: Parser[() => State] = Command.applyEffect(parser)(apply) val command: Parser[() => State] = Command.applyEffect(parser)(apply)
} }

View File

@ -20,21 +20,24 @@ import sbt.io.IO
/** /**
* Represents (potentially) transient settings added into a build via commands/user. * Represents (potentially) transient settings added into a build via commands/user.
* *
* @param currentBuild * @param currentBuild The current sbt build with which we scope new settings
* The current sbt build with which we scope new settings * @param currentProject The current project with which we scope new settings.
* @param currentProject * @param original The original list of settings for this build.
* The current project with which we scope new settings. * @param append Settings which have been defined and appended that may ALSO be saved to disk.
* @param original * @param rawAppend Settings which have been defined and appended which CANNOT be saved to disk
* The original list of settings for this build. * @param currentEval A compiler we can use to compile new setting strings.
* @param append
* Settings which have been defined and appended that may ALSO be saved to disk.
* @param rawAppend
* Settings which have been defined and appended which CANNOT be saved to disk
* @param currentEval
* A compiler we can use to compile new setting strings.
*/ */
final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) { final case class SessionSettings(
assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") currentBuild: URI,
currentProject: Map[URI, String],
original: Seq[Setting[_]],
append: SessionMap,
rawAppend: Seq[Setting[_]],
currentEval: () => Eval
) {
assert(currentProject contains currentBuild,
s"Current build ($currentBuild) not associated with a current project.")
/** /**
* Modifiy the current state. * Modifiy the current state.
@ -44,7 +47,8 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str
* @param eval The mechanism to compile new settings. * @param eval The mechanism to compile new settings.
* @return A new SessionSettings object * @return A new SessionSettings object
*/ */
def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval) def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings =
copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval)
/** /**
* @return The current ProjectRef with which we scope settings. * @return The current ProjectRef with which we scope settings.
@ -86,6 +90,7 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str
object SessionSettings { object SessionSettings {
/** A session setting is simply a tuple of a Setting[_] and the strings which define it. */ /** A session setting is simply a tuple of a Setting[_] and the strings which define it. */
type SessionSetting = (Setting[_], Seq[String]) type SessionSetting = (Setting[_], Seq[String])
type SessionMap = Map[ProjectRef, Seq[SessionSetting]] type SessionMap = Map[ProjectRef, Seq[SessionSetting]]
type SbtConfigFile = (File, Seq[String]) type SbtConfigFile = (File, Seq[String])
@ -305,7 +310,7 @@ save, save-all
def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) } def range: Parser[(Int, Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo) }
/** The raw implementation of the session command. */ /** The raw implementation of the session command. */
def command(s: State) = Command.applyEffect(parser) { def command(s: State): Parser[() => State] = Command.applyEffect(parser) {
case p: Print => if (p.all) printAllSettings(s) else printSettings(s) case p: Print => if (p.all) printAllSettings(s) else printSettings(s)
case v: Save => if (v.all) saveAllSettings(s) else saveSettings(s) case v: Save => if (v.all) saveAllSettings(s) else saveSettings(s)
case c: Clear => if (c.all) clearAllSettings(s) else clearSettings(s) case c: Clear => if (c.all) clearAllSettings(s) else clearSettings(s)

View File

@ -116,7 +116,7 @@ private[sbt] object SettingCompletions {
val full = for { val full = for {
defineKey <- scopedKeyParser(keyMap, settings, context) defineKey <- scopedKeyParser(keyMap, settings, context)
a <- assign(defineKey) a <- assign(defineKey)
deps <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key))) _ <- valueParser(defineKey, a, inputScopedKey(keyFilter(defineKey.key)))
} yield () // parser is currently only for completion and the parsed data structures are not used } yield () // parser is currently only for completion and the parsed data structures are not used
matched(full) | any.+.string matched(full) | any.+.string