From fa487ac8eab93e6c03badf8336fd1f4a4f035819 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 6 Apr 2017 13:28:18 +0100 Subject: [PATCH 1/4] Upgrade from -Ymacro-no-expand to -Ymacro-expand:none --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0ff74cdd6..3c611b4b4 100644 --- a/build.sbt +++ b/build.sbt @@ -315,7 +315,7 @@ lazy val docProjects: ScopeFilter = ScopeFilter( inConfigurations(Compile) ) def fullDocSettings = Util.baseScalacOptions ++ Docs.settings ++ Sxr.settings ++ Seq( - scalacOptions += "-Ymacro-no-expand", // for both sxr and doc + scalacOptions += "-Ymacro-expand:none", // for both sxr and doc sources in sxr := { val allSources = (sources ?? Nil).all(docProjects).value allSources.flatten.distinct From 2caa324d521fd83d3421bcdd1d344bfbeda62bb8 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 7 Apr 2017 13:13:02 +0100 Subject: [PATCH 2/4] Solve a scaladoc issue --- main/src/main/scala/sbt/internal/parser/SbtParser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/parser/SbtParser.scala b/main/src/main/scala/sbt/internal/parser/SbtParser.scala index 4a78663a9..5c81bd9f2 100644 --- a/main/src/main/scala/sbt/internal/parser/SbtParser.scala +++ b/main/src/main/scala/sbt/internal/parser/SbtParser.scala @@ -125,7 +125,7 @@ private[sbt] case class SbtParser(file: File, lines: Seq[String]) extends Parsed case _ => false } - /** + /* * See BugInParser * @param t - tree * @param originalStatement - original From 6c07972dd04d090f013c4796cfdcec14f47b0876 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 6 Apr 2017 10:53:12 +0100 Subject: [PATCH 3/4] Cleanup Command/Help and usage --- .../main/scala/sbt/BasicCommandStrings.scala | 22 +- .../src/main/scala/sbt/BasicCommands.scala | 202 +++++++++++------- main-command/src/main/scala/sbt/Command.scala | 163 +++++++++----- .../src/main/scala/sbt/CommandUtil.scala | 26 ++- main/src/main/scala/sbt/Main.scala | 112 +++++----- main/src/main/scala/sbt/Project.scala | 37 ++-- main/src/main/scala/sbt/TemplateCommand.scala | 88 ++++---- .../scala/sbt/internal/CommandStrings.scala | 98 +++++---- main/src/main/scala/sbt/internal/Load.scala | 24 ++- .../scala/sbt/internal/PluginsDebug.scala | 113 +++++++--- .../sbt/internal/ProjectNavigation.scala | 36 ++-- .../scala/sbt/internal/SessionSettings.scala | 37 ++-- .../sbt/internal/SettingCompletions.scala | 2 +- 13 files changed, 603 insertions(+), 357 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index 7a9e127d7..dcdd4d4d1 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -17,15 +17,15 @@ object BasicCommandStrings { val TerminateAction: String = Exit def helpBrief = (HelpCommand, s"Displays this help message or prints detailed help on requested commands (run '$HelpCommand ').") - def helpDetailed = HelpCommand + """ + def helpDetailed = s"""$HelpCommand Prints a help summary. -""" + HelpCommand + """ +$HelpCommand Prints detailed help for command . -""" + HelpCommand + """ +$HelpCommand 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 Help(brief, detailed) } + private[this] def logLevelDetail(level: Level.Value): String = s"""$level @@ -130,24 +131,21 @@ object BasicCommandStrings { val AliasCommand = "alias" def AliasDetailed = - AliasCommand + """ + s"""$AliasCommand Prints a list of defined aliases. -""" + - AliasCommand + """ name +$AliasCommand 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. Whenever `name` is entered, the corresponding `value` is run. If any argument is provided to `name`, it is appended as argument to `value`. -""" + - AliasCommand + """ name= +$AliasCommand name= Removes the alias for `name`.""" @@ -194,9 +192,9 @@ object BasicCommandStrings { def IfLast = "iflast" def IfLastCommon = "If there are no more commands after this one, 'command' is run." def IfLastDetailed = - IfLast + """ + s"""$IfLast - """ + IfLastCommon + $IfLastCommon""" val ContinuousExecutePrefix = "~" def continuousDetail = "Executes the specified command whenever source files change." diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index d1fdf7892..66dff7a1a 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -2,7 +2,15 @@ package sbt import sbt.util.Level 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.inc.classpath.ClasspathUtilities.toLoader import sbt.internal.inc.ModuleUtilities @@ -19,32 +27,34 @@ import sbt.io.IO import scala.util.control.NonFatal object BasicCommands { - lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure, - stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, client, read, alias) ++ compatCommands + lazy val allBasicCommands: Seq[Command] = Seq( + 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 ignore = Command.command(FailureWall)(idFun) + def nop: Command = Command.custom(s => success(() => s)) + 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] = - token(Level.Debug.toString) | token(Level.Info.toString) | token(Level.Warn.toString) | token(Level.Error.toString) - private[this] def earlyParser: State => Parser[String] = (s: State) => - (token(EarlyCommand + "(") flatMap { _ => - otherCommandParser(s) <~ token(")") - }) | - (token("-") flatMap { _ => - levelParser - }) + Iterator(Level.Debug, Level.Info, Level.Warn, Level.Error) map (l => token(l.toString)) reduce (_ | _) + + private[this] def earlyParser: State => Parser[String] = (s: State) => { + val p1 = token(EarlyCommand + "(") flatMap (_ => otherCommandParser(s) <~ token(")")) + val p2 = token("-") flatMap (_ => levelParser) + p1 | p2 + } + 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) => - a ++ - (try b.help(s) catch { case NonFatal(ex) => Help.empty }) - } + val h = (Help.empty /: s.definedCommands)((a, b) => + a ++ (try b.help(s) catch { case NonFatal(_) => Help.empty })) val helpCommands = h.detail.keySet val spacedArg = singleArgument(helpCommands).? applyEffect(spacedArg)(runHelp(s, h)) @@ -52,24 +62,22 @@ object BasicCommands { def runHelp(s: State, h: Help)(arg: Option[String]): State = { - val message = try - Help.message(h, arg) - catch { - case NonFatal(ex) => - ex.toString - } + val message = try Help.message(h, arg) catch { case NonFatal(ex) => ex.toString } System.out.println(message) s } - def completionsCommand = Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser) - def completionsParser(state: State) = + def completionsCommand: Command = + Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser) + + def completionsParser(state: State): Parser[() => State] = { val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s } val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state)) } + def runCompletions(state: State)(input: String): State = { Parser.completions(state.combinedParser, input, 9).get map { c => if (c.isEmpty) input else input + c.append @@ -82,34 +90,40 @@ object BasicCommands { def multiParser(s: State): Parser[List[String]] = { 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] = 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] = matched(s.combinedParser | token(any, hide = const(true))) - def ifLast = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser) { (s, arg) => - if (s.remainingCommands.isEmpty) arg :: s else s - } - def append = Command(AppendCommand, Help.more(AppendCommand, AppendLastDetailed))(otherCommandParser) { (s, arg) => - s.copy(remainingCommands = s.remainingCommands :+ Exec(arg, s.source)) - } + def ifLast: Command = Command(IfLast, Help.more(IfLast, IfLastDetailed))(otherCommandParser)((s, arg) => + if (s.remainingCommands.isEmpty) arg :: s else s) + + def append: Command = + 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( Command.command(Compat.ClearOnFailure) { s => s.log.warn(Compat.ClearOnFailureDeprecated) 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.copy(onFailure = Some(Exec(arg, s.source))) }, @@ -119,43 +133,55 @@ object BasicCommands { } ) - def clearOnFailure = 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 clearOnFailure: Command = Command.command(ClearOnFailure)(s => s.copy(onFailure = None)) + + 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 updated = if (stack.isEmpty) s.remove(OnFailureStack) else s.put(OnFailureStack, stack.tail) updated.copy(onFailure = stack.headOption.flatten) } - def reboot = Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser) { (s, full) => - s.reboot(full) - } - def rebootParser(s: State) = token(Space ~> "full" ^^^ true) ?? false + def reboot: Command = + Command(RebootCommand, Help.more(RebootCommand, RebootDetailed))(rebootParser)((s, full) => s reboot full) - 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)) => 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 loaded = args.map(arg => ModuleUtilities.getObject(arg, loader).asInstanceOf[State => State]) (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] = { val base = StringBasic & not('-' ~> any.*, "Class name cannot start with '-'.") def single(s: String) = Completions.single(Completion.displayOnly(s)) - val compl = TokenCompletions.fixed((seen, level) => if (seen.startsWith("-")) Completions.nil else single("")) + val compl = TokenCompletions.fixed((seen, _) => + if (seen.startsWith("-")) Completions.nil else single("")) token(base, compl) } + private[this] def classpathOptionParser: Parser[Seq[String]] = token(("-cp" | "-classpath") ~> Space) ~> classpathStrings <~ token(Space) + private[this] def classpathStrings: Parser[Seq[String]] = token(StringBasic.map(s => IO.pathSplit(s).toSeq), "") - 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) => withAttribute(s, Watched.Configuration, "Continuous execution not configured.") { w => 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] = Command.applyEffect(HistoryCommands.actionParser) { histFun => 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 prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " } val reader = new FullReader(history, s.combinedParser) val line = reader.readLine(prompt) line match { case Some(line) => - val newState = s.copy(onFailure = Some(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 case None => s.setInteractive(false) } } - def client = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser) - def clientParser(s0: State) = + def client: Command = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser) + + 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 => val arguments = inputArg.toList ++ (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 readParser(s: State) = + def read: Command = + 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 portAndSuccess = token(OptSpace) ~> Port portAndSuccess || files } + def doRead(s: State)(arg: Either[Int, Seq[File]]): State = arg match { case Left(portAndSuccess) => val port = math.abs(portAndSuccess) val previousSuccess = portAndSuccess >= 0 readMessage(port, previousSuccess) match { - case Some(message) => (message :: (ReadCommand + " " + port) :: s).copy(onFailure = Some(Exec(ReadCommand + " " + (-port), s.source))) + case Some(message) => + (message :: (ReadCommand + " " + port) :: s).copy( + onFailure = Some(Exec(ReadCommand + " " + (-port), s.source)) + ) case None => System.err.println("Connection closed.") s.fail @@ -226,12 +263,14 @@ object BasicCommands { case Right(from) => val notFound = notReadable(from) 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 { s.log.error("Command file(s) not readable: \n\t" + notFound.mkString("\n\t")) s } } + private def readMessage(port: Int, previousSuccess: Boolean): Option[String] = { // split into two connections because this first connection ends the previous communication @@ -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 assign = token(OptSpace ~ '=' ~ OptSpace) val sfree = removeAliases(s) @@ -273,34 +312,45 @@ object BasicCommands { s.copy(definedCommands = newAlias(name, value) +: s.definedCommands) def removeAliases(s: State): State = removeTagged(s, CommandAliasKey) - def removeAlias(s: State, name: String): State = s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c))) - def removeTagged(s: State, tag: AttributeKey[_]): State = s.copy(definedCommands = removeTagged(s.definedCommands, tag)) - def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = as.filter(c => !(c.tags contains tag)) + def removeAlias(s: State, name: String): State = + s.copy(definedCommands = s.definedCommands.filter(c => !isAliasNamed(name, c))) + + def removeTagged(s: State, tag: AttributeKey[_]): State = + s.copy(definedCommands = removeTagged(s.definedCommands, tag)) + + def removeTagged(as: Seq[Command], tag: AttributeKey[_]): Seq[Command] = + as.filter(c => !(c.tags contains tag)) def isAliasNamed(name: String, c: Command): Boolean = isNamed(name, getAlias(c)) - def isNamed(name: String, alias: Option[(String, String)]): Boolean = alias match { case None => false; case Some((n, _)) => name == n } + + def isNamed(name: String, alias: Option[(String, String)]): Boolean = + alias match { case None => false; case Some((n, _)) => name == n } def getAlias(c: Command): Option[(String, String)] = c.tags get CommandAliasKey - def printAlias(s: State, name: String): Unit = printAliases(aliases(s, (n, v) => n == name)) + def printAlias(s: State, name: String): Unit = printAliases(aliases(s, (n, _) => n == name)) def printAliases(s: State): Unit = printAliases(allAliases(s)) + def printAliases(as: Seq[(String, String)]): Unit = for ((name, value) <- as) println("\t" + name + " = " + value) def aliasNames(s: State): Seq[String] = allAliases(s).map(_._1) - def allAliases(s: State): Seq[(String, String)] = aliases(s, (n, v) => true) + def allAliases(s: State): Seq[(String, String)] = aliases(s, (_, _) => true) def aliases(s: State, pred: (String, String) => Boolean): Seq[(String, String)] = s.definedCommands.flatMap(c => getAlias(c).filter(tupled(pred))) def newAlias(name: String, value: String): Command = - Command.make(name, (name, "'" + value + "'"), "Alias of '" + value + "'")(aliasBody(name, value)).tag(CommandAliasKey, (name, value)) + 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] = { val aliasRemoved = removeAlias(state, name) // 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 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) } @@ -310,5 +360,9 @@ object BasicCommands { 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." + ) } diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 974e94987..edf1a0d4e 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -8,98 +8,155 @@ import sbt.internal.util.complete.{ DefaultParsers, EditDistance, Parser } import sbt.internal.util.Types.const import sbt.internal.util.{ AttributeKey, AttributeMap, Util } +/** + * An operation that can be executed from the sbt console. + * + *

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 { def help: State => Help def parser: State => Parser[() => State] + def tags: AttributeMap 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.") - def tag[T](key: AttributeKey[T], value: T): SimpleCommand = new SimpleCommand(name, help0, parser, tags.put(key, value)) + +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, s"'$name' is not a valid command name.") + 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)" } -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 { import DefaultParsers._ - def command(name: String, briefHelp: String, detail: String)(f: State => State): Command = command(name, Help(name, (name, briefHelp), detail))(f) - def command(name: String, help: Help = Help.empty)(f: State => State): Command = make(name, help)(state => success(() => f(state))) + // Lowest-level command construction + + 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 = 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 = 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 = args(name, display, Help(name, briefHelp, detail))(f) - 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)) + // create ArbitraryCommand - def single(name: String, briefHelp: (String, String), detail: String)(f: (State, String) => State): Command = - single(name, Help(name, briefHelp, detail))(f) - 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 customHelp(parser: State => Parser[() => State], help: State => Help): Command = + new ArbitraryCommand(parser, help, AttributeMap.empty) - def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command = customHelp(parser, const(help)) - def customHelp(parser: State => Parser[() => State], help: State => Help): Command = new ArbitraryCommand(parser, help, AttributeMap.empty) - def arb[T](parser: State => Parser[T], help: Help = Help.empty)(effect: (State, T) => State): Command = custom(applyEffect(parser)(effect), help) + def custom(parser: State => Parser[() => State], help: Help = Help.empty): Command = + customHelp(parser, const(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] = 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] = - { - val (simple, arbs) = separateCommands(cmds) - state => (simpleParser(simple)(state) /: arbs.map(_ parser state)) { _ | _ } - } + def combine(cmds: Seq[Command]): State => Parser[() => State] = { + val (simple, arbs) = separateCommands(cmds) + state => (simpleParser(simple)(state) /: arbs.map(_ parser state))(_ | _) + } + private[this] def separateCommands(cmds: Seq[Command]): (Seq[SimpleCommand], Seq[ArbitraryCommand]) = 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] = simpleParser(cmds.map(sc => (sc.name, argParser(sc))).toMap) - private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] = - { - def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None) - s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s) - } + + private[this] def argParser(sc: SimpleCommand): State => Parser[() => State] = { + def usageError = s"${sc.name} usage:" + Help.message(sc.help0, None) + s => (Parser.softFailure(usageError, definitive = true): Parser[() => State]) | sc.parser(s) + } 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 { case None => failure(invalidValue("command", commandMap.keys)(id)) case Some(c) => c(state) - } - } + }) def invalidValue(label: String, allowed: Iterable[String])(value: String): String = - "Not a valid " + label + ": " + value + similar(value, allowed) - def similar(value: String, allowed: Iterable[String]): String = - { - val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil - if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")") - } + s"Not a valid $label: $value" + similar(value, allowed) + + def similar(value: String, allowed: Iterable[String]): String = { + val suggested = if (value.length > 2) suggestions(value, allowed.toSeq) else Nil + if (suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")") + } + 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 = - 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 spacedC(name: String, c: Parser[Char]): Parser[String] = ((c & opOrIDSpaced(name)) ~ c.+) map { case (f, rem) => (f +: rem).mkString } } @@ -110,19 +167,25 @@ trait Help { def more: Set[String] 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) } + object Help { 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 = apply(briefHelp :: Nil, detailedHelp) def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String]): Help = apply(briefHelp, detailedHelp, Set.empty[String]) + def apply(briefHelp: Seq[(String, String)], detailedHelp: Map[String, String], more: Set[String]): Help = new Help0(briefHelp, detailedHelp, more) @@ -138,15 +201,17 @@ object Help { case Some(x) => detail(x, h.detail) case None => val brief = aligned(" ", " ", h.brief).mkString("\n", "\n", "\n") - val more = h.more.toSeq.sorted + val more = h.more if (more.isEmpty) brief else - brief + "\n" + moreMessage(more) + brief + "\n" + moreMessage(more.toSeq.sorted) } + def moreMessage(more: Seq[String]): String = more.mkString("More command help available using 'help ' for:\n ", ", ", "\n") } + trait CommandDefinitions extends (State => State) { def commands: Seq[Command] = ReflectUtilities.allVals[Command](this).values.toSeq def apply(s: State): State = s ++ commands diff --git a/main-command/src/main/scala/sbt/CommandUtil.scala b/main-command/src/main/scala/sbt/CommandUtil.scala index f2f53943c..e71a49eda 100644 --- a/main-command/src/main/scala/sbt/CommandUtil.scala +++ b/main-command/src/main/scala/sbt/CommandUtil.scala @@ -10,9 +10,9 @@ import sbt.internal.util.complete.DefaultParsers._ import sbt.io.IO object CommandUtil { - def readLines(files: Seq[File]): Seq[String] = files flatMap (line => IO.readLines(line)) flatMap processLine - def processLine(s: String) = { val trimmed = s.trim; if (ignoreLine(trimmed)) None else Some(trimmed) } - def ignoreLine(s: String) = s.isEmpty || s.startsWith("#") + def readLines(files: Seq[File]): Seq[String] = files flatMap (IO.readLines(_)) flatMap processLine + def processLine(s: String): Option[String] = { val s2 = s.trim; if (ignoreLine(s2)) None else Some(s2) } + def ignoreLine(s: String): Boolean = s.isEmpty || s.startsWith("#") private def canRead = (_: File).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 def bootDirectory(state: State): File = - try { state.configuration.provider.scalaProvider.launcher.bootDirectory } - catch { case e: NoSuchMethodError => new File(".").getAbsoluteFile } + try state.configuration.provider.scalaProvider.launcher.bootDirectory + catch { case _: NoSuchMethodError => new File(".").getAbsoluteFile } def aligned(pre: String, sep: String, in: Seq[(String, String)]): Seq[String] = if (in.isEmpty) Nil else { - val width = in.map(_._1.length).max - in.map { case (a, b) => (pre + fill(a, width) + sep + b) } + val width = in.iterator.map(_._1.length).max + 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 = - (s get key) match { + s get key match { case None => s.log.error(ifMissing); s.fail case Some(nav) => f(nav) @@ -41,6 +42,7 @@ object CommandUtil { val arg = (NotSpaceClass ~ any.*) map { case (ns, s) => (ns +: s).mkString } token(Space) ~> token(arg examples exampleStrings) } + def detail(selected: String, detailMap: Map[String, String]): String = detailMap.get(selected) match { case Some(exactDetail) => exactDetail @@ -51,9 +53,11 @@ object CommandUtil { else layoutDetails(details) } 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] = { val pattern = Pattern.compile(selected, HelpPatternFlags) @@ -69,9 +73,9 @@ object CommandUtil { Nil } } + def layoutDetails(details: Map[String, String]): String = details.map { case (k, v) => k + "\n\n " + v } mkString ("\n", "\n\n", "\n") final val HelpPatternFlags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE - } diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index e74fe1fb7..2e31bfdc3 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -4,28 +4,14 @@ package sbt import sbt.internal.{ - Act, - Aggregation, - BuildStructure, - BuildUnit, - CommandExchange, - CommandStrings, - EvaluateConfigurations, - Inspect, - IvyConsole, - Load, - LoadedBuildUnit, - Output, - PluginsDebug, - ProjectNavigation, - Script, - SessionSettings, - SetResult, - SettingCompletions, - LogManager, - DefaultBackgroundJobService + Act, Aggregation, BuildStructure, BuildUnit, CommandExchange, CommandStrings, + EvaluateConfigurations, 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.internal.util.{ AttributeKey, AttributeMap, ConsoleOut, GlobalLogging, LineRange, MainAppender, SimpleReader, Types } import sbt.util.{ Level, Logger } import sbt.internal.util.complete.{ DefaultParsers, Parser } @@ -124,6 +110,7 @@ final class ScriptMain extends xsbti.AppMain { )) } } + final class ConsoleMain extends xsbti.AppMain { def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = runManaged(initialState( @@ -175,11 +162,17 @@ object BuiltinCommands { 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 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, - 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 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, + 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 boot = Command.make(BootCommand)(bootParser) @@ -215,6 +208,7 @@ object BuiltinCommands { val allPluginNames = e.structure.units.values.flatMap(u => list(u.unit)).toSeq.distinct if (allPluginNames.isEmpty) "" else allPluginNames.mkString("Available Plugins: ", ", ", "") } + def aboutScala(s: State, e: Extracted): String = { val scalaVersion = e.getOpt(Keys.scalaVersion) @@ -228,6 +222,7 @@ object BuiltinCommands { case (None, None, None) => "" } } + def aboutString(s: State): String = { 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 |""".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 } - 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)) { case (s: State, (verbosity: Int, selected: Option[String])) => if (selected.isEmpty) System.out.println(preamble) @@ -304,20 +307,22 @@ object BuiltinCommands { 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 } - 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) s } + private[this] def loadedEval(s: State, arg: String): Unit = { val extracted = Project extract s import extracted._ val result = session.currentEval().eval(arg, srcName = "", imports = autoImports(extracted)) s.log.info(s"ans: ${result.tpe} = ${result.getValue(currentLoader)}") } + private[this] def rawEval(s: State, arg: String): Unit = { val app = s.configuration.provider val classpath = app.mainClasspath ++ app.scalaProvider.jars @@ -325,15 +330,17 @@ object BuiltinCommands { 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 = { 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 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) } @@ -354,7 +361,9 @@ object BuiltinCommands { arg, LineRange(0, 0) )(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.debug(setResult.verboseSummary) reapply(setResult.session, structure, s) @@ -369,7 +378,7 @@ object BuiltinCommands { s } - def lastGrep = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { + def lastGrep: Command = Command(LastGrepCommand, lastGrepBrief, lastGrepDetailed)(lastGrepParser) { case (s, (pattern, Some(sks))) => val (str, _, display) = extractLast(s) 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) } - def act = Command.customHelp(Act.actParser, actHelp) - def actHelp = (s: State) => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s) + def act: Command = Command.customHelp(Act.actParser, actHelp) + def actHelp: State => Help = s => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ keysHelp(s) + def keysHelp(s: State): Help = if (Project.isProjectLoaded(s)) Help.detailOnly(taskDetail(allTaskAndSettingKeys(s))) else Help.empty - def plugins = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s => + + def plugins: Command = Command.command(PluginsCommand, pluginsBrief, pluginsDetailed) { s => val helpString = PluginsDebug.helpAll(s) System.out.println(helpString) s } + val pluginParser: State => Parser[AutoPlugin] = s => { val autoPlugins: Map[String, AutoPlugin] = PluginsDebug.autoPluginMap(s) 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) System.out.println(helpString) s } - def projects = Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) { - case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds) - case (s, None) => showProjects(s); s - } + def projects: Command = + Command(ProjectsCommand, (ProjectsCommand, projectsBrief), projectsDetailed)(s => projectsParser(s).?) { + case (s, Some(modifyBuilds)) => transformExtraBuilds(s, modifyBuilds) + case (s, None) => showProjects(s); s + } + def showProjects(s: State): Unit = { val extracted = Project extract s import extracted._ @@ -502,6 +517,7 @@ object BuiltinCommands { listBuild(curi, structure.units(curi), true, 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 = { val original = Project.extraBuilds(s) @@ -553,11 +569,11 @@ object BuiltinCommands { Nil def loadProject: Command = - Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser) { (s, arg) => + Command(LoadProject, LoadProjectBrief, LoadProjectDetailed)(loadProjectParser)((s, arg) => 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 def loadProjectImpl: Command = Command(LoadProjectImpl)(_ => Project.loadActionParser)(doLoadProject) diff --git a/main/src/main/scala/sbt/Project.scala b/main/src/main/scala/sbt/Project.scala index 5489b6eb1..b661ef2cc 100755 --- a/main/src/main/scala/sbt/Project.scala +++ b/main/src/main/scala/sbt/Project.scala @@ -361,8 +361,10 @@ object Project extends ProjectExtra { def getProjectForReference(ref: Reference, structure: BuildStructure): Option[ResolvedProject] = 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: LoadedBuild): Option[ResolvedProject] = getProject(ref, structure.units) + def getProject(ref: ProjectRef, units: Map[URI, LoadedBuildUnit]): Option[ResolvedProject] = (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) previousOnUnload(s.runExitHooks()) } + def setProject(session: SessionSettings, structure: BuildStructure, s: State): State = { 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 current(state: State): ProjectRef = session(state).current + def updateCurrent(s: State): State = { val structure = Project.structure(s) @@ -410,6 +414,7 @@ object Project extends ProjectExtra { .put(templateResolverInfos.key, trs) s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands) } + 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) } @@ -528,20 +533,21 @@ object Project extends ProjectExtra { def relation(structure: BuildStructure, actual: Boolean)(implicit display: Show[ScopedKey[_]]): Relation[ScopedKey[_], ScopedKey[_]] = 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)) - ((Relation.empty: Rel) /: cMap) { - case (r, (key, value)) => - r + (key, value.dependencies) - } + val emptyRelation = Relation.empty[ScopedKey[_], ScopedKey[_]] + (emptyRelation /: cMap) { case (r, (key, value)) => r + (key, value.dependencies) } } def showDefinitions(key: AttributeKey[_], defs: Seq[Scope])(implicit display: Show[ScopedKey[_]]): String = showKeys(defs.map(scope => ScopedKey(scope, key))) + def showUses(defs: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String = showKeys(defs) + private[this] def showKeys(s: Seq[ScopedKey[_]])(implicit display: Show[ScopedKey[_]]): String = 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 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 loadAction(s: State, action: LoadAction.Value) = action match { + + def loadAction(s: State, action: LoadAction.Value): (State, File) = action match { case Return => projectReturn(s) match { - case current :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo) - case _ => sys.error("Not currently in a plugin definition") + case _ /* current */ :: returnTo :: rest => (setProjectReturn(s, returnTo :: rest), returnTo) + case _ => sys.error("Not currently in a plugin definition") } + case Current => val base = s.configuration.baseDirectory projectReturn(s) match { case Nil => (setProjectReturn(s, base :: Nil), base); case x :: xs => (s, x) } + case Plugins => - val (newBase, oldStack) = if (Project.isProjectLoaded(s)) - (Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s)) - else // support changing to the definition project if it fails to load - (BuildPaths.projectStandard(s.baseDir), s.baseDir :: Nil) + val (newBase, oldStack) = + if (Project.isProjectLoaded(s)) + (Project.extract(s).currentUnit.unit.plugins.base, projectReturn(s)) + 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) (newS, newBase) } @@ -603,6 +613,7 @@ object Project extends ProjectExtra { val mfi = EvaluateTask.minForcegcInterval(extracted, extracted.structure) 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])] = { val extracted = Project.extract(state) EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, config) diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index 2447779d5..ca31433c3 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -2,47 +2,43 @@ package sbt import java.lang.reflect.InvocationTargetException import java.io.File + +import sbt.io._, syntax._ import sbt.util._ -import sbt.internal.util._ +import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._ 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.internal.librarymanagement.IvyConfiguration +import sbt.internal.inc.classpath.ClasspathUtilities +import BasicCommandStrings._, BasicKeys._, Command.applyEffect private[sbt] object TemplateCommandUtil { - def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser) - def templateCommandParser(state: State) = - { - 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) - }) - } + def templateCommand: Command = + Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser) - private def run(infos: List[TemplateResolverInfo], arguments: List[String], config: AppConfiguration, - ivyConf: IvyConfiguration, globalBase: File, ivyScala: Option[IvyScala], log: Logger): Unit = + def templateCommandParser(state: State): Parser[() => State] = { + 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 => val loader = infoLoader(info, config, ivyConf, globalBase, ivyScala, log) val hit = tryTemplate(info, arguments, loader) @@ -54,6 +50,7 @@ private[sbt] object TemplateCommandUtil { case Some(_) => // do nothing case None => System.err.println("Template not found for: " + arguments.mkString(" ")) } + private def tryTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Boolean = { val resultObj = call(info.implementationClass, "isDefined", loader)( @@ -61,12 +58,21 @@ private[sbt] object TemplateCommandUtil { )(arguments.toArray) resultObj.asInstanceOf[Boolean] } + private def runTemplate(info: TemplateResolverInfo, arguments: List[String], loader: ClassLoader): Unit = 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 = - ClasspathUtilities.toLoader(classpathForInfo(info, ivyConf, globalBase, ivyScala, log), config.provider.loader) - private def call(interfaceClassName: String, methodName: String, loader: ClassLoader)(argTypes: Class[_]*)(args: AnyRef*): AnyRef = + + private def infoLoader( + info: TemplateResolverInfo, config: AppConfiguration, ivyConf: IvyConfiguration, globalBase: File, + 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 interface = interfaceClass.getDeclaredConstructor().newInstance().asInstanceOf[AnyRef] @@ -76,10 +82,14 @@ private[sbt] object TemplateCommandUtil { case e: InvocationTargetException => throw e.getCause } } + private def getInterfaceClass(name: String, loader: ClassLoader) = Class.forName(name, true, loader) // 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 templatesBaseDirectory = new File(globalBase, "templates") diff --git a/main/src/main/scala/sbt/internal/CommandStrings.scala b/main/src/main/scala/sbt/internal/CommandStrings.scala index d061f296e..d6d2383ce 100644 --- a/main/src/main/scala/sbt/internal/CommandStrings.scala +++ b/main/src/main/scala/sbt/internal/CommandStrings.scala @@ -18,9 +18,9 @@ object CommandStrings { val BootCommand = "boot" val EvalCommand = "eval" - val evalBrief = (EvalCommand + " ", "Evaluates a Scala expression and prints the result and type.") + val evalBrief = (s"$EvalCommand ", "Evaluates a Scala expression and prints the result and type.") val evalDetailed = - EvalCommand + """ + s"""$EvalCommand Evaluates the given Scala expression and prints the result and type.""" @@ -31,8 +31,8 @@ object CommandStrings { s"""$multiTaskSyntax $multiTaskBrief""" - def multiTaskSyntax = s"""$MultiTaskCommand +""" - def multiTaskBrief = """Executes all of the specified tasks concurrently.""" + def multiTaskSyntax = s"$MultiTaskCommand +" + def multiTaskBrief = "Executes all of the specified tasks concurrently." def showHelp = Help(ShowCommand, (s"$ShowCommand ", showBrief), showDetailed) def showBrief = "Displays the result of evaluating the setting or task associated with 'key'." @@ -57,26 +57,26 @@ $ShowCommand val lastGrepBrief = (LastGrepCommand, "Shows lines from the last output for 'key' that match 'pattern'.") val lastGrepDetailed = - LastGrepCommand + """ + s"""$LastGrepCommand Displays lines from the logging of previous commands that match `pattern`. -""" + LastGrepCommand + """ [key] +$LastGrepCommand [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. 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 lastDetailed = - LastCommand + """ + s"""$LastCommand Prints the logging for the previous command, typically at a more verbose level. -""" + LastCommand + """ +$LastCommand 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 + " +", "Executes tasks and displays the equivalent command lines.") + val exportBrief = (s"$ExportCommand +", "Executes tasks and displays the equivalent command lines.") val exportDetailed = s"""$ExportCommand [--last] + Runs the specified tasks and prints the equivalent command lines or other exportable information for those runs. @@ -92,7 +92,7 @@ $ShowCommand """ val InspectCommand = "inspect" - val inspectBrief = (InspectCommand + " [uses|tree|definitions] ", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.") + val inspectBrief = (s"$InspectCommand [uses|tree|definitions] ", "Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.") val inspectDetailed = s""" |$InspectCommand | @@ -133,7 +133,7 @@ $ShowCommand val SetCommand = "set" val setBrief = (s"$SetCommand [every] ", "Evaluates a Setting and applies it to the current project.") val setDetailed = - SetCommand + """ [every] + s"""$SetCommand [every] Applies the given setting to the current project: 1) Constructs the expression provided as an argument by compiling and loading it. @@ -150,68 +150,82 @@ $ShowCommand """ 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 tasksPreamble = commonPreamble("tasks") + """ Tasks produce values. Use the 'show' command to run the task and print the resulting value.""" - def commonPreamble(label: String) = """ -This is a list of %s defined for the current project. -It does not list the scopes the %] + $label [-(v|-vv|...|-V)] [] -% - Restricts the %+ +$ProjectsCommand add + 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 -""" + ProjectsCommand + """ remove + +$ProjectsCommand remove + Removes extra builds from this session. Builds explicitly listed in the build definition are not affected by this command. """ @@ -250,8 +264,8 @@ Syntax summary def LoadProjectImpl = "loadp" def LoadProject = "reload" def LoadProjectBrief = (LoadProject, "(Re)loads the current project or changes to plugins project or returns from it.") - def LoadProjectDetailed = LoadProject + - s""" + def LoadProjectDetailed = + s"""$LoadProject \t(Re)loads the project in the current directory. @@ -266,7 +280,7 @@ $LoadProject return def InitCommand = "initialize" def InitBrief = (InitCommand, "Initializes command processing.") def InitDetailed = - InitCommand + """ + s"""$InitCommand Initializes command processing. Runs the following commands. diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index 5556c9c93..a3faaaf7d 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -242,16 +242,26 @@ private[sbt] object Load { } // 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 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) - 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[_]] = { @@ -291,7 +301,8 @@ private[sbt] object Load { () => 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 = mkEval(defs.target ++ plugs.classpath, defs.base, options) @@ -335,7 +346,8 @@ private[sbt] object Load { 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 = { diff --git a/main/src/main/scala/sbt/internal/PluginsDebug.scala b/main/src/main/scala/sbt/internal/PluginsDebug.scala index 18dfdc277..6b756453b 100644 --- a/main/src/main/scala/sbt/internal/PluginsDebug.scala +++ b/main/src/main/scala/sbt/internal/PluginsDebug.scala @@ -9,7 +9,11 @@ import Plugins._ import PluginsDebug._ 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`. * 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 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] = 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 = { val (activated, deactivated) = Util.separate(toEnable(notFoundKey, context)) { case pa: PluginActivated => Left(pa) 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) } + private[this] def debugDeactivated(notFoundKey: String, deactivated: Seq[EnableDeactivated]): String = { val (impossible, possible) = Util.separate(deactivated) { @@ -41,23 +48,27 @@ private[sbt] class PluginsDebug(val available: List[AutoPlugin], val nameToKey: if (possible.nonEmpty) { val explained = possible.map(explainPluginEnable) 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}" 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 } else if (impossible.isEmpty) s"No available plugin provides key $notFoundKey." else { 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 = - if (context.enabled.contains(plugin)) activatedHelp(plugin) - else deactivatedHelp(plugin, context) + if (context.enabled.contains(plugin)) activatedHelp(plugin) else deactivatedHelp(plugin, context) + private def activatedHelp(plugin: AutoPlugin): String = { 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))}" prefix + keysString + confsString } + private def deactivatedHelp(plugin: AutoPlugin, context: Context): String = { val prefix = s"${plugin.label} is NOT activated." @@ -105,6 +117,7 @@ private[sbt] object PluginsDebug { import extracted._ structure.units.values.toList.flatMap(availableAutoPlugins).map(plugin => (plugin.label, plugin)).toMap } + private[this] def availableAutoPlugins(build: LoadedBuildUnit): Seq[AutoPlugin] = build.unit.plugins.detected.autoPlugins map { _.value } @@ -120,7 +133,10 @@ private[sbt] object PluginsDebug { lazy val debug = PluginsDebug(context.available) if (!pluginsThisBuild.contains(plugin)) { 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)) debug.activatedHelp(plugin) else { @@ -128,7 +144,8 @@ private[sbt] object PluginsDebug { val definedInAggregated = thisAggregated.filter(ref => definesPlugin(projectForRef(ref))) if (definedInAggregated.nonEmpty) { 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 { val base = debug.deactivatedHelp(plugin, context) 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 = { val keyR = definedKeys(available) @@ -154,36 +171,58 @@ private[sbt] object PluginsDebug { * @param deducePlugin The function used to compute the model. * @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. */ sealed abstract class PluginEnable + /** Describes a [[plugin]] that is already activated in the [[context]].*/ final case class PluginActivated(plugin: AutoPlugin, context: Context) extends PluginEnable + sealed abstract class EnableDeactivated extends PluginEnable + /** 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]]. * @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 enablingPlugins [[AutoPlugin]]s that are not currently enabled, 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 enablingPlugins [[AutoPlugin]]s that are not currently enabled, + * 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 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. * 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. */ 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 = if (context.enabled.contains(plugin)) 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. // This can include more plugins than just `minRequiredPlugins` because the plugins required for `plugin` // 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 // 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. - val extraPlugins = incrementalModel.toSet -- minRequiredPlugins -- initialModel + val extraPlugins = incrementalModel -- minRequiredPlugins -- initialModel // Plugins that will no longer be enabled as a result of enabling `plugin`. val willRemove = initialModel -- incrementalModel @@ -318,8 +360,10 @@ private[sbt] object PluginsDebug { private[this] def excludedPluginError(transitive: Boolean)(dependency: AutoPlugin) = s"Required ${transitiveString(transitive)}dependency ${dependency.label} was excluded." + private[this] def excludedPluginsError(transitive: Boolean)(dependencies: List[AutoPlugin]) = s"Required ${transitiveString(transitive)}dependencies were excluded:\n\t${labels(dependencies).mkString("\n\t")}" + private[this] def transitiveString(transitive: Boolean) = if (transitive) "(transitive) " else "" @@ -328,6 +372,7 @@ private[sbt] object PluginsDebug { private[this] def requiredPlugin(plugin: AutoPlugin) = s"Required plugin ${plugin.label} not present." + private[this] def requiredPlugins(plugins: List[AutoPlugin]) = 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) = s"Enabling ${base.label} will also enable ${plugin.label}" + private[this] def willAddPlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = 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) = s"Enabling ${base.label} will disable ${plugin.label}" + private[this] def willRemovePlugins(base: AutoPlugin)(plugins: List[AutoPlugin]) = 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 = str(deactivate)(deactivate1, deactivateN) + private[this] def deactivateN(plugins: List[DeactivatePlugin]): String = plugins.map(deactivateString).mkString("These plugins need to be deactivated:\n\t", "\n\t", "") + private[this] def deactivate1(deactivate: DeactivatePlugin): String = s"Need to deactivate ${deactivateString(deactivate)}" + private[this] def deactivateString(d: DeactivatePlugin): String = { val removePluginsString: String = @@ -377,8 +427,17 @@ private[sbt] object PluginsDebug { private[this] def pluginImpossible(plugin: AutoPlugin, contradictions: Set[AutoPlugin]): String = str(contradictions.toList)(pluginImpossible1(plugin), pluginImpossibleN(plugin)) - 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." - private[this] def pluginImpossibleN(plugin: AutoPlugin)(contradictions: List[AutoPlugin]): String = - 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." + private[this] def pluginImpossible1(plugin: AutoPlugin)(contradiction: AutoPlugin): String = { + val s1 = s"There is no way to enable plugin ${plugin.label}." + val s2 = s"It (or its dependencies) requires plugin ${contradiction.label} to both be present and absent." + 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" + } } diff --git a/main/src/main/scala/sbt/internal/ProjectNavigation.scala b/main/src/main/scala/sbt/internal/ProjectNavigation.scala index c254505f5..63cbb49ce 100644 --- a/main/src/main/scala/sbt/internal/ProjectNavigation.scala +++ b/main/src/main/scala/sbt/internal/ProjectNavigation.scala @@ -4,29 +4,29 @@ package sbt package internal -import sbt.internal.util.complete - -import Project.updateCurrent -import Keys.sessionSettings -import complete.{ DefaultParsers, Parser } -import DefaultParsers._ 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 { def command(s: State): Parser[() => State] = if (s get sessionSettings isEmpty) failure("No project loaded") else (new ProjectNavigation(s)).command } + final class ProjectNavigation(s: State) { - val extracted = Project extract s + val extracted: Extracted = Project extract s 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) 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 = 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 } - 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 = if (structure.units(uri).defined.contains(to)) setProject(uri, to) 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 = if (structure.units contains newBuild) @@ -51,14 +52,9 @@ final class ProjectNavigation(s: State) { else fail("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).") - def fail(msg: String): State = - { - s.log.error(msg) - s.fail - } + def fail(msg: String): State = { s.log.error(msg); s.fail } - import complete.Parser._ - import complete.Parsers._ + import Parser._, complete.Parsers._ val parser: Parser[Option[ResolvedReference]] = { @@ -66,6 +62,8 @@ final class ProjectNavigation(s: State) { val root = token('/' ^^^ rootRef) success(None) | some(token(Space) ~> (root | reference)) } + def rootRef = ProjectRef(currentRef.build, getRoot(currentRef.build)) + val command: Parser[() => State] = Command.applyEffect(parser)(apply) } diff --git a/main/src/main/scala/sbt/internal/SessionSettings.scala b/main/src/main/scala/sbt/internal/SessionSettings.scala index 0d1c5c4e5..564a14411 100755 --- a/main/src/main/scala/sbt/internal/SessionSettings.scala +++ b/main/src/main/scala/sbt/internal/SessionSettings.scala @@ -20,21 +20,24 @@ import sbt.io.IO /** * Represents (potentially) transient settings added into a build via commands/user. * - * @param currentBuild - * The current sbt build with which we scope new settings - * @param currentProject - * The current project with which we scope new settings. - * @param original - * The original list of settings for this build. - * @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. + * @param currentBuild The current sbt build with which we scope new settings + * @param currentProject The current project with which we scope new settings. + * @param original The original list of settings for this build. + * @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) { - assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") +final case class SessionSettings( + 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. @@ -44,7 +47,8 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str * @param eval The mechanism to compile new settings. * @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. @@ -86,6 +90,7 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str object SessionSettings { /** A session setting is simply a tuple of a Setting[_] and the strings which define it. */ type SessionSetting = (Setting[_], Seq[String]) + type SessionMap = Map[ProjectRef, Seq[SessionSetting]] 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) } /** 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 v: Save => if (v.all) saveAllSettings(s) else saveSettings(s) case c: Clear => if (c.all) clearAllSettings(s) else clearSettings(s) diff --git a/main/src/main/scala/sbt/internal/SettingCompletions.scala b/main/src/main/scala/sbt/internal/SettingCompletions.scala index 334b3c1d3..a1629ee85 100644 --- a/main/src/main/scala/sbt/internal/SettingCompletions.scala +++ b/main/src/main/scala/sbt/internal/SettingCompletions.scala @@ -116,7 +116,7 @@ private[sbt] object SettingCompletions { val full = for { defineKey <- scopedKeyParser(keyMap, settings, context) 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 matched(full) | any.+.string From ec15837f437be49d9cb5899bb52077ad6e47827f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 6 Apr 2017 11:42:21 +0100 Subject: [PATCH 4/4] Replace unnecessary uses of Command.make with Command.apply --- .../src/main/scala/sbt/BasicCommands.scala | 49 +++++++++---------- main/src/main/scala/sbt/TemplateCommand.scala | 26 +++++----- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index 66dff7a1a..c6033d812 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -68,15 +68,13 @@ object BasicCommands { } def completionsCommand: Command = - Command.make(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser) + Command(CompletionsCommand, CompletionsBrief, CompletionsDetailed)(completionsParser)(runCompletions(_)(_)) - def completionsParser(state: State): Parser[() => State] = - { - val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s } - val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) - - applyEffect(token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")))(runCompletions(state)) - } + def completionsParser(state: State): Parser[String] = { + val notQuoted = (NotQuoted ~ any.*) map { case (nq, s) => nq ++ s } + val quotedOrUnquotedSingleArgument = Space ~> (StringVerbatim | StringEscapable | notQuoted) + token(quotedOrUnquotedSingleArgument ?? "" examples ("", " ")) + } def runCompletions(state: State)(input: String): State = { Parser.completions(state.combinedParser, input, 9).get map { @@ -220,24 +218,22 @@ object BasicCommands { } } - def client: Command = Command.make(Client, Help.more(Client, ClientDetailed))(clientParser) + def client: Command = Command(Client, Help.more(Client, ClientDetailed))(_ => clientParser)(runClient) - def clientParser(s0: State): Parser[() => State] = - { - val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) - applyEffect(p)({ inputArg => - val arguments = inputArg.toList ++ - (s0.remainingCommands match { - case e :: Nil if e.commandLine == "shell" => Nil - case xs => xs map { _.commandLine } - }) - NetworkClient.run(arguments) - "exit" :: s0.copy(remainingCommands = Nil) + def clientParser: Parser[Seq[String]] = + (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) + + def runClient(s0: State, inputArg: Seq[String]): State = { + val arguments = inputArg.toList ++ + (s0.remainingCommands match { + case e :: Nil if e.commandLine == "shell" => Nil + case xs => xs map (_.commandLine) }) - } + NetworkClient.run(arguments) + "exit" :: s0.copy(remainingCommands = Nil) + } - def read: Command = - Command.make(ReadCommand, Help.more(ReadCommand, ReadDetailed))(s => applyEffect(readParser(s))(doRead(s))) + def read: Command = Command(ReadCommand, Help.more(ReadCommand, ReadDetailed))(readParser)(doRead(_)(_)) def readParser(s: State): Parser[Either[Int, Seq[File]]] = { @@ -282,14 +278,13 @@ object BasicCommands { } } - def alias: Command = Command.make(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s => + def alias: Command = Command(AliasCommand, Help.more(AliasCommand, AliasDetailed)) { s => val name = token(OpOrID.examples(aliasNames(s): _*)) val assign = token(OptSpace ~ '=' ~ OptSpace) val sfree = removeAliases(s) val to = matched(sfree.combinedParser, partial = true).failOnException | any.+.string - val base = (OptSpace ~> (name ~ (assign ~> to.?).?).?) - applyEffect(base)(t => runAlias(s, t)) - } + OptSpace ~> (name ~ (assign ~> to.?).?).? + }(runAlias) def runAlias(s: State, args: Option[(String, Option[Option[String]])]): State = args match { diff --git a/main/src/main/scala/sbt/TemplateCommand.scala b/main/src/main/scala/sbt/TemplateCommand.scala index ca31433c3..c258c8da7 100644 --- a/main/src/main/scala/sbt/TemplateCommand.scala +++ b/main/src/main/scala/sbt/TemplateCommand.scala @@ -10,29 +10,29 @@ import xsbti.AppConfiguration import sbt.librarymanagement._ import sbt.internal.librarymanagement.IvyConfiguration import sbt.internal.inc.classpath.ClasspathUtilities -import BasicCommandStrings._, BasicKeys._, Command.applyEffect +import BasicCommandStrings._, BasicKeys._ private[sbt] object TemplateCommandUtil { def templateCommand: Command = - Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser) + Command(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)(runTemplate) - def templateCommandParser(state: State): Parser[() => State] = { - val p = (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) + private def templateCommandParser(state: State): Parser[Seq[String]] = + (token(Space) ~> repsep(StringBasic, token(Space))) | (token(EOF) map (_ => Nil)) + + private def runTemplate(state: State, inputArg: Seq[String]): State = { 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) - }) + 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(