mirror of https://github.com/sbt/sbt.git
Adds templateResolvers and `new` command
This adds `new` command, which helps create a new build definition. The
`new` command is extensible via a mechanism called the template
resolver,
which evaluates the arbitrary arguments passed to the command to find
and run a template.
As a reference implementation [Giter8][g8] is provided as follows:
sbt new eed3si9n/hello.g8
This will run eed3si9n/hello.g8 using Giter8.
[g8]: http://www.foundweekends.org/giter8/
This commit is contained in:
parent
e54d4ed8fd
commit
73a427c0b8
|
|
@ -192,7 +192,7 @@ lazy val commandProj = (project in file("main-command")).
|
|||
settings(
|
||||
testedBaseSettings,
|
||||
name := "Command",
|
||||
libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson),
|
||||
libraryDependencies ++= Seq(launcherInterface, sjsonNewScalaJson, templateResolverApi, giter8),
|
||||
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",
|
||||
contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats
|
||||
).
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ object BasicCommandStrings {
|
|||
val CompletionsCommand = "completions"
|
||||
val Exit = "exit"
|
||||
val Quit = "quit"
|
||||
val TemplateCommand = "new"
|
||||
|
||||
/** The command name to terminate the program.*/
|
||||
val TerminateAction: String = Exit
|
||||
|
|
@ -32,6 +33,10 @@ object BasicCommandStrings {
|
|||
def CompletionsDetailed = "Displays a list of completions for the given argument string (run 'completions <string>')."
|
||||
def CompletionsBrief = (CompletionsCommand, CompletionsDetailed)
|
||||
|
||||
def templateBrief = (TemplateCommand, "Creates a new sbt build.")
|
||||
def templateDetailed = TemplateCommand + """ [--options] <template>
|
||||
Create a new sbt build based on the given template."""
|
||||
|
||||
def HistoryHelpBrief = (HistoryCommands.Start -> "History command help. Lists and describes all history commands.")
|
||||
def historyHelp = Help(Nil, (HistoryHelpBrief +: HistoryCommands.descriptions).toMap, Set(HistoryCommands.Start))
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import sbt.io.IO
|
|||
import scala.util.control.NonFatal
|
||||
|
||||
object BasicCommands {
|
||||
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
|
||||
lazy val allBasicCommands = Seq(nop, ignore, help, completionsCommand, templateCommand, multi, ifLast, append, setOnFailure, clearOnFailure,
|
||||
stashOnFailure, popOnFailure, reboot, call, early, exit, continuous, history, shell, server, client, read, alias) ++ compatCommands
|
||||
|
||||
def nop = Command.custom(s => success(() => s))
|
||||
|
|
@ -82,7 +82,32 @@ object BasicCommands {
|
|||
state
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:main-command/src/main/scala/sbt/BasicCommands.scala
|
||||
def multiParser(s: State): Parser[List[String]] =
|
||||
=======
|
||||
def templateCommand = Command.make(TemplateCommand, templateBrief, templateDetailed)(templateCommandParser)
|
||||
def templateCommandParser(state: State) =
|
||||
{
|
||||
val p = token(Space).* ~> repsep(StringBasic, token(Space))
|
||||
val trs = (state get templateResolvers) match {
|
||||
case Some(trs) => trs.toList
|
||||
case None => Nil
|
||||
}
|
||||
applyEffect(p)({ inputArg =>
|
||||
val arguments = inputArg.toList ++ state.remainingCommands
|
||||
trs find { tr =>
|
||||
tr.isDefined(arguments.toArray)
|
||||
} match {
|
||||
case Some(tr) => tr.run(arguments.toArray)
|
||||
case None =>
|
||||
System.err.println("Template not found for: " + arguments.mkString(" "))
|
||||
}
|
||||
"exit" :: state.copy(remainingCommands = Nil)
|
||||
})
|
||||
}
|
||||
|
||||
def multiParser(s: State): Parser[Seq[String]] =
|
||||
>>>>>>> 954e744... Adds templateResolvers and `new` command:main/command/src/main/scala/sbt/BasicCommands.scala
|
||||
{
|
||||
val nonSemi = token(charClass(_ != ';').+, hide = const(true))
|
||||
(token(';' ~> OptSpace) flatMap { _ => matched((s.combinedParser & nonSemi) | nonSemi) <~ token(OptSpace) } map (_.trim)).+ map { _.toList }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package sbt
|
|||
import java.io.File
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.inc.classpath.ClassLoaderCache
|
||||
import sbt.template.TemplateResolver
|
||||
|
||||
object BasicKeys {
|
||||
val historyPath = AttributeKey[Option[File]]("history", "The location where command line history is persisted.", 40)
|
||||
|
|
@ -13,4 +14,5 @@ object BasicKeys {
|
|||
private[sbt] val classLoaderCache = AttributeKey[ClassLoaderCache]("class-loader-cache", "Caches class loaders based on the classpath entries and last modified times.", 10)
|
||||
private[sbt] val OnFailureStack = AttributeKey[List[Option[Exec]]]("on-failure-stack", "Stack that remembers on-failure handlers.", 10)
|
||||
private[sbt] val explicitGlobalLogLevels = AttributeKey[Boolean]("explicit-global-log-levels", "True if the global logging levels were explicitly set by the user.", 10)
|
||||
private[sbt] val templateResolvers = AttributeKey[Seq[TemplateResolver]]("templateResolvers", "List of template resolvers.", 1000)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ object Defaults extends BuildCommon {
|
|||
maxErrors :== 100,
|
||||
fork :== false,
|
||||
initialize :== {},
|
||||
templateResolvers :== Nil,
|
||||
forcegc :== sys.props.get("sbt.task.forcegc").map(java.lang.Boolean.parseBoolean).getOrElse(GCUtil.defaultForceGarbageCollection),
|
||||
minForcegcInterval :== GCUtil.defaultMinForcegcInterval,
|
||||
serverPort := 5000 + (Hash.toHex(Hash(appConfiguration.value.baseDirectory.toString)).## % 1000)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package sbt
|
||||
|
||||
import sbt.template.TemplateResolver
|
||||
|
||||
object Giter8TemplateResolver extends TemplateResolver {
|
||||
def isDefined(args0: Array[String]): Boolean =
|
||||
{
|
||||
val args = args0.toList filterNot { _.startsWith("-") }
|
||||
// Mandate .g8
|
||||
val Github = """^([^\s/]+)/([^\s/]+?)(?:\.g8)$""".r
|
||||
val Local = """^file://(\S+)(?:\.g8)(?:/)?$""".r
|
||||
object GitUrl {
|
||||
val NativeUrl = "^(git[@|://].*)$".r
|
||||
val HttpsUrl = "^(https://.*)$".r
|
||||
val HttpUrl = "^(http://.*)$".r
|
||||
val SshUrl = "^(ssh://.*)$".r
|
||||
def unapplySeq(s: Any): Option[List[String]] =
|
||||
NativeUrl.unapplySeq(s) orElse
|
||||
HttpsUrl.unapplySeq(s) orElse
|
||||
HttpUrl.unapplySeq(s) orElse
|
||||
SshUrl.unapplySeq(s)
|
||||
}
|
||||
args.headOption match {
|
||||
case Some(Github(_, _)) => true
|
||||
case Some(Local(_)) => true
|
||||
case GitUrl(uri) => uri contains (".g8")
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
def run(args: Array[String]): Unit =
|
||||
giter8.Giter8.run(args)
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ import org.apache.ivy.core.module.{ descriptor, id }
|
|||
import descriptor.ModuleDescriptor, id.ModuleRevisionId
|
||||
import testing.Framework
|
||||
import KeyRanks._
|
||||
import sbt.template.TemplateResolver
|
||||
|
||||
import sbt.internal.{ BuildStructure, LoadedBuild, PluginDiscovery, BuildDependencies, SessionSettings, LogManager }
|
||||
import sbt.io.FileFilter
|
||||
|
|
@ -403,6 +404,7 @@ object Keys {
|
|||
val sbtVersion = SettingKey[String]("sbt-version", "Provides the version of sbt. This setting should be not be modified.", AMinusSetting)
|
||||
val sbtBinaryVersion = SettingKey[String]("sbt-binary-version", "Defines the binary compatibility version substring.", BPlusSetting)
|
||||
val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile' and 'update'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.", BSetting)
|
||||
val templateResolvers = SettingKey[Seq[TemplateResolver]]("templateResolvers", "Template resolvers used for 'new'.", BSetting)
|
||||
|
||||
// special
|
||||
val sessionVars = AttributeKey[SessionVar.Map]("session-vars", "Bindings that exist for the duration of the session.", Invisible)
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ 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,
|
||||
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, BasicCommands.server, BasicCommands.client, continuous, eval, alias, append, last, lastGrep, export, boot, nop, call, exit, early, initialize, act) ++
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ package sbt
|
|||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.util.Locale
|
||||
import Project._
|
||||
import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, serverPort, thisProject, thisProjectRef, watch }
|
||||
import Project.{ Initialize => _, Setting => _, _ }
|
||||
import Keys.{ appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, templateResolvers, serverPort, thisProject, thisProjectRef, watch }
|
||||
import Scope.{ GlobalScope, ThisScope }
|
||||
import Def.{ Flattened, Initialize, ScopedKey, Setting }
|
||||
import sbt.internal.{ Load, BuildStructure, LoadedBuild, LoadedBuildUnit, SettingGraph, SettingCompletions, AddSettings, SessionSettings, LogManager }
|
||||
|
|
@ -17,6 +17,7 @@ import sbt.internal.util.complete.DefaultParsers
|
|||
import sbt.librarymanagement.Configuration
|
||||
import sbt.util.Eval
|
||||
import sjsonnew.JsonFormat
|
||||
import sbt.template.TemplateResolver
|
||||
|
||||
import language.experimental.macros
|
||||
|
||||
|
|
@ -420,12 +421,19 @@ object Project extends ProjectExtra {
|
|||
val allCommands = commandsIn(ref) ++ commandsIn(BuildRef(ref.build)) ++ (commands in Global get structure.data toList)
|
||||
val history = get(historyPath) flatMap idFun
|
||||
val prompt = get(shellPrompt)
|
||||
val trs = (templateResolvers in Global get structure.data).toList.flatten
|
||||
val watched = get(watch)
|
||||
val port: Option[Int] = get(serverPort)
|
||||
val commandDefs = allCommands.distinct.flatten[Command].map(_ tag (projectCommand, true))
|
||||
val newDefinedCommands = commandDefs ++ BasicCommands.removeTagged(s.definedCommands, projectCommand)
|
||||
<<<<<<< HEAD
|
||||
val newAttrs0 = setCond(Watched.Configuration, watched, s.attributes).put(historyPath.key, history)
|
||||
val newAttrs = setCond(serverPort.key, port, newAttrs0)
|
||||
=======
|
||||
val newAttrs = setCond(Watched.Configuration, watched, s.attributes).
|
||||
put(historyPath.key, history).
|
||||
put(templateResolvers.key, trs)
|
||||
>>>>>>> 954e744... Adds templateResolvers and `new` command
|
||||
s.copy(attributes = setCond(shellPrompt.key, prompt, newAttrs), definedCommands = newDefinedCommands)
|
||||
}
|
||||
def setCond[T](key: AttributeKey[T], vopt: Option[T], attributes: AttributeMap): AttributeMap =
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ object PluginDiscovery {
|
|||
"sbt.plugins.IvyPlugin" -> sbt.plugins.IvyPlugin,
|
||||
"sbt.plugins.JvmPlugin" -> sbt.plugins.JvmPlugin,
|
||||
"sbt.plugins.CorePlugin" -> sbt.plugins.CorePlugin,
|
||||
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin
|
||||
"sbt.plugins.JUnitXmlReportPlugin" -> sbt.plugins.JUnitXmlReportPlugin,
|
||||
"sbt.plugins.Giter8TemplatePlugin" -> sbt.plugins.Giter8TemplatePlugin
|
||||
)
|
||||
val detectedAutoPugins = discover[AutoPlugin](AutoPlugins)
|
||||
val allAutoPlugins = (defaultAutoPlugins ++ detectedAutoPugins.modules) map {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package sbt
|
||||
package plugins
|
||||
|
||||
import Def.Setting
|
||||
import Keys._
|
||||
|
||||
/**
|
||||
* An experimental plugin that adds the ability for Giter8 templates to be resolved
|
||||
*/
|
||||
object Giter8TemplatePlugin extends AutoPlugin {
|
||||
override def requires = CorePlugin
|
||||
override def trigger = allRequirements
|
||||
|
||||
override lazy val globalSettings: Seq[Setting[_]] =
|
||||
Seq(
|
||||
templateResolvers += Giter8TemplateResolver
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
### new command and templateResolvers
|
||||
|
||||
sbt 0.13.13 adds `new` command, which helps create a new build definition.
|
||||
The `new` command is extensible via a mechanism called the template resolver,
|
||||
which evaluates the arguments passed to the command to find and run a template.
|
||||
As a reference implementation [Giter8][g8] is provided as follows:
|
||||
|
||||
sbt new eed3si9n/hello.g8
|
||||
|
||||
This will run eed3si9n/hello.g8 using Giter8.
|
||||
|
||||
[@eed3si9n]: https://github.com/eed3si9n
|
||||
[g8]: http://www.foundweekends.org/giter8/
|
||||
|
|
@ -92,6 +92,8 @@ object Dependencies {
|
|||
val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.13.4"
|
||||
val specs2 = "org.specs2" %% "specs2" % "2.4.17"
|
||||
val junit = "junit" % "junit" % "4.11"
|
||||
val templateResolverApi = "org.scala-sbt" % "template-resolver" % "0.1"
|
||||
val giter8 = "org.foundweekends.giter8" %% "giter8" % "0.7.0"
|
||||
|
||||
private def scala211Module(name: String, moduleVersion: String) = Def setting (
|
||||
scalaBinaryVersion.value match {
|
||||
|
|
|
|||
Loading…
Reference in New Issue