├── .java-version ├── LICENSE ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── slash.jpg └── src ├── main └── scala │ └── sbt │ └── sbtslash │ ├── SlashDisplay.scala │ ├── SlashInspect.scala │ ├── SlashParser.scala │ ├── SlashScope.scala │ ├── SlashSyntax.scala │ └── SlashSyntaxPlugin.scala └── sbt-test └── slash └── unified ├── build.sbt ├── project └── plugins.sbt └── test /.java-version: -------------------------------------------------------------------------------- 1 | 1.6 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sbt-slash 2 | ========= 3 | 4 | sbt-slash is a sbt plugin that introduces unified slash syntax to both the sbt shell and build.sbt. 5 | See also [Unification of sbt shell notation and build.sbt DSL][contrib] 6 | 7 | ![slash](slash.jpg?raw=true) 8 | 9 | setup 10 | ----- 11 | 12 | This is an auto plugin, so you need sbt 0.13.5+. Put this in `project/plugins.sbt` or `~/.sbt/0.13/plugins/slash.sbt`: 13 | 14 | ```scala 15 | addSbtPlugin("com.eed3si9n" % "sbt-slash" % "0.1.0") 16 | ``` 17 | 18 | usage 19 | ----- 20 | 21 | In addition to the current parser, this plugin adds `//intask/key` where `` is the Scala identifier notation for the configurations like `Compile` and `Test`. 22 | 23 | These examples work both from the shell 24 | 25 | ```scala 26 | > show Global/cancelable 27 | > ThisBuild/scalaVersion 28 | > Test/compile 29 | > root/Compile/compile/scalacOptions 30 | ``` 31 | 32 | The output of the `inspect` command is copy-pastable 33 | 34 | ``` 35 | > inspect compile 36 | [info] Task: sbt.inc.Analysis 37 | [info] Description: 38 | [info] Compiles sources. 39 | [info] Provided by: 40 | [info] {file:/xxx/}hellotest/compile:compile 41 | [info] Defined at: 42 | [info] (sbt.Defaults) Defaults.scala:327 43 | [info] Dependencies: 44 | [info] Compile/manipulateBytecode 45 | [info] Compile/incCompileSetup 46 | [info] Reverse dependencies: 47 | [info] Compile/products 48 | [info] Compile/printWarnings 49 | [info] Compile/discoveredSbtPlugins 50 | [info] Compile/discoveredMainClasses 51 | [info] Delegates: 52 | [info] Compile/compile 53 | [info] compile 54 | [info] ThisBuild/Compile/compile 55 | [info] ThisBuild/compile 56 | [info] Zero/Compile/compile 57 | [info] Global/compile 58 | [info] Related: 59 | [info] Test/compile 60 | ``` 61 | 62 | The unified slash syntax also works in `build.sbt` 63 | 64 | ```scala 65 | lazy val root = (project in file(".")) 66 | .settings( 67 | Global / cancelable := true, 68 | ThisBuild / scalaVersion := "2.11.11", 69 | Test / test := (), 70 | Compile / scalacOptions += "-deprecation", 71 | Compile / console / scalacOptions += "-Ywarn-numeric-widen" 72 | ) 73 | ``` 74 | 75 | notes 76 | ----- 77 | 78 | If this syntax is proven to be useful, we intend to introduce this as part of sbt in the future. 79 | 80 | [contrib]: https://contributors.scala-lang.org/t/unification-of-sbt-shell-notation-and-build-sbt-dsl/913 81 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val commonSettings: Seq[Setting[_]] = Seq( 2 | version in ThisBuild := "0.1.0", 3 | organization in ThisBuild := "com.eed3si9n" 4 | ) 5 | 6 | lazy val root = (project in file(".")). 7 | settings(commonSettings). 8 | settings( 9 | sbtPlugin := true, 10 | name := "sbt-slash", 11 | description := "sbt plugin to create a single fat jar", 12 | licenses := Seq("Apache v2" -> url("https://github.com/sbt/sbt-assembly/blob/master/LICENSE")), 13 | scalacOptions := Seq("-deprecation", "-unchecked"), 14 | libraryDependencies ++= Seq( 15 | ), 16 | ScriptedPlugin.scriptedSettings, 17 | scriptedLaunchOpts := { scriptedLaunchOpts.value ++ 18 | Seq("-Xmx1024M", "-XX:MaxPermSize=256M", "-Dplugin.version=" + version.value) 19 | }, 20 | scriptedBufferLog := false, 21 | publishMavenStyle := false, 22 | bintrayOrganization := None, 23 | bintrayRepository := "sbt-plugins" 24 | ) 25 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.16-M1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies += { 2 | "org.scala-sbt" % "scripted-plugin" % sbtVersion.value 3 | } 4 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.3.0") 5 | -------------------------------------------------------------------------------- /slash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbt/sbt-slash/b852a828bf266226a63b569c6fa9ec6178846090/slash.jpg -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashDisplay.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | object SlashDisplay { 5 | // from Def.scala 6 | def showRelativeKey(current: ProjectRef, multi: Boolean, keyNameColor: Option[String] = None): Show[ScopedKey[_]] = new Show[ScopedKey[_]] { 7 | def apply(key: ScopedKey[_]) = 8 | SlashScope.display(key.scope, 9 | Def.colored(key.key.label, keyNameColor), 10 | ref => displayRelative(current, multi, ref, true)) 11 | } 12 | 13 | private[sbt] def displayRelative(current: ProjectRef, multi: Boolean, project: Reference, 14 | trailingSlash: Boolean): String = { 15 | val slash = if (trailingSlash) "/" else "" 16 | project match { 17 | case BuildRef(current.build) => "ThisBuild" + slash 18 | case `current` => if (multi) current.project + slash else "" 19 | case ProjectRef(current.build, x) => x + slash 20 | case _ => Reference.display(project) + slash 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashInspect.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | import complete.{ DefaultParsers, Parser } 5 | import DefaultParsers._ 6 | import Def.ScopedKey 7 | import java.io.File 8 | import CommandStrings._ 9 | 10 | object SlashInspect { 11 | def inspect = Command(InspectCommand, inspectBrief, inspectDetailed)(parser) { 12 | case (s, (option, sk)) => 13 | s.log.info(output(s, option, sk)) 14 | s 15 | } 16 | 17 | sealed trait Mode 18 | final case class Details(actual: Boolean) extends Mode 19 | private[this] final class Opt(override val toString: String) extends Mode 20 | val DependencyTree: Mode = new Opt("tree") 21 | val Uses: Mode = new Opt("inspect") 22 | val Definitions: Mode = new Opt("definitions") 23 | 24 | def parser: State => Parser[(SlashInspect.Mode, ScopedKey[_])] = (s: State) => spacedModeParser(s) flatMap { 25 | case opt @ (Uses | Definitions) => allKeyParser(s).map(key => (opt, Def.ScopedKey(Global, key))) 26 | case opt @ (DependencyTree | Details(_)) => spacedKeyParser(s).map(key => (opt, key)) 27 | } 28 | val spacedModeParser: (State => Parser[Mode]) = (s: State) => { 29 | val actual = "actual" ^^^ Details(true) 30 | val tree = "tree" ^^^ DependencyTree 31 | val uses = "uses" ^^^ Uses 32 | val definitions = "definitions" ^^^ Definitions 33 | token(Space ~> (tree | actual | uses | definitions)) ?? Details(false) 34 | } 35 | 36 | def allKeyParser(s: State): Parser[AttributeKey[_]] = 37 | { 38 | val keyMap = Project.structure(s).index.keyMap 39 | token(Space ~> (ID !!! "Expected key" examples keyMap.keySet)) flatMap { key => Act.getKey(keyMap, key, idFun) } 40 | } 41 | 42 | val spacedKeyParser: State => Parser[ScopedKey[_]] = 43 | (s: State) => Act.requireSession(s, token(Space) ~> SlashParser.scopedKeyParser(s)) 44 | 45 | def output(s: State, option: Mode, sk: Def.ScopedKey[_]): String = 46 | { 47 | val extracted = Project.extract(s) 48 | import extracted.{ showKey => _, _ } 49 | // This is the change that I made 50 | implicit val show = SlashDisplay.showRelativeKey(session.current, structure.allProjects.size > 1, None) 51 | option match { 52 | case Details(actual) => 53 | Project.details(structure, actual, sk.scope, sk.key) 54 | case DependencyTree => 55 | val basedir = new File(Project.session(s).current.build) 56 | Project.settingGraph(structure, basedir, sk).dependsAscii(get(sbt.Keys.asciiGraphWidth)) 57 | case Uses => 58 | Project.showUses(Project.usedBy(structure, true, sk.key)) 59 | case Definitions => 60 | Project.showDefinitions(sk.key, Project.definitions(structure, true, sk.key)) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashParser.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | import sbt._ 5 | import Keys.{ sessionSettings, thisProject } 6 | import java.io.File 7 | import BasicCommandStrings._ 8 | import complete.{ DefaultParsers, Parser } 9 | import CommandStrings.{ MultiTaskCommand, ShowCommand } 10 | import Aggregation.{ KeyValue, Values } 11 | import Def.ScopedKey 12 | import Act.{ key, defaultConfigurations, select, extraAxis, toAxis, examplesStrict, examples => examples2, 13 | nonEmptyConfig, filterStrings } 14 | import SlashDisplay.showRelativeKey 15 | 16 | object SlashParser { 17 | import DefaultParsers._ 18 | 19 | private val GlobalString = "*" 20 | private val GlobalIdent = "Global" 21 | private val ZeroIdent = "Zero" 22 | private val ThisBuildIdent = "ThisBuild" 23 | 24 | // new separator for unified shell syntax. this allows optional whitespace around /. 25 | private val spacedSlash: Parser[Unit] = token(OptSpace ~> '/' <~ OptSpace).examples("/").map(_ => ()) 26 | private val spacedComma = token(OptSpace ~ ',' ~ OptSpace) 27 | /** Parses a single letter, according to Char.isUpper, into a Char. */ 28 | private lazy val Upper = charClass(_.isUpper, "upper") 29 | /** Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and contain zero or more [[ScalaIDChar]]s after that.*/ 30 | private lazy val CapitalizedID = identifier(Upper, ScalaIDChar) 31 | 32 | def act = Command.customHelp(actParser, actHelp) 33 | private def actHelp = (s: State) => CommandStrings.showHelp ++ CommandStrings.multiTaskHelp ++ BuiltinCommands.keysHelp(s) 34 | 35 | // borrowed from sbt 36 | private def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s)) 37 | private def actParser0(state: State): Parser[() => State] = 38 | { 39 | val extracted = Project extract state 40 | import extracted.{ showKey, structure } 41 | import Aggregation.evaluatingParser 42 | actionParser.flatMap { action => 43 | val akp = aggregatedKeyParser(extracted) 44 | def evaluate(kvs: Seq[ScopedKey[_]]): Parser[() => State] = { 45 | val preparedPairs = anyKeyValues(structure, kvs) 46 | val showConfig = Aggregation.defaultShow(state, showTasks = action == ShowAction) 47 | evaluatingParser(state, structure, showConfig)(preparedPairs) map { evaluate => 48 | () => { 49 | val keyStrings = preparedPairs.map(pp => showKey(pp.key)).mkString(", ") 50 | state.log.debug("Evaluating tasks: " + keyStrings) 51 | evaluate() 52 | } 53 | } 54 | } 55 | action match { 56 | case SingleAction => akp flatMap evaluate 57 | case ShowAction | MultiAction => 58 | rep1sep(akp, token(Space)).flatMap(kvss => evaluate(kvss.flatten)) 59 | } 60 | } 61 | } 62 | 63 | // borrowed from sbt 64 | // the index should be an aggregated index for proper tab completion 65 | private def scopedKeyAggregated(current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], structure: BuildStructure): KeysParser = 66 | for { 67 | selected <- scopedKeySelected(structure.index.aggregateKeyIndex, current, defaultConfigs, structure.index.keyMap, structure.data) 68 | } yield Aggregation.aggregate(selected.key, selected.mask, structure.extra) 69 | 70 | // borrowed from sbt 71 | private def scopedKeySelected(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], 72 | keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ParsedKey] = 73 | scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => 74 | select(choices, data)(showRelativeKey(current, index.buildURIs.size > 1)) 75 | } 76 | 77 | private def scopedKeyFull(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] = 78 | { 79 | def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String], baseMask: ScopeMask): Seq[Parser[ParsedKey]] = 80 | for { 81 | conf <- configs(confAmb, defaultConfigs, proj, index) 82 | } yield for { 83 | taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap) 84 | task = resolveTask(taskAmb) 85 | key <- key(index, proj, conf, task, keyMap) 86 | extra <- extraAxis(keyMap, IMap.empty) 87 | } yield { 88 | val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true) 89 | new ParsedKey(makeScopedKey(proj, conf, task, extra, key), mask) 90 | } 91 | 92 | def fullKey = 93 | for { 94 | rawProject <- optProjectRef(index, current) 95 | proj = resolveProject(rawProject, current) 96 | confAmb <- configIdent(index.configs(proj), index.configs(proj) map SlashScope.guessConfigIdent) 97 | partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false) 98 | } yield taskKeyExtra(proj, confAmb, partialMask) 99 | 100 | val globalIdent = token(GlobalIdent ~ spacedSlash) ^^^ ParsedGlobal 101 | def globalKey = 102 | for { 103 | g <- globalIdent 104 | } yield taskKeyExtra(None, ParsedZero, ScopeMask(true, true, false, false)) 105 | 106 | globalKey | fullKey 107 | } 108 | 109 | 110 | def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] = 111 | ScopedKey(Scope(toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key) 112 | 113 | def config(confs: Set[String]): Parser[ParsedAxis[String]] = 114 | { 115 | val sep = ':' !!! "Expected ':' (if selecting a configuration)" 116 | token((GlobalString ^^^ ParsedZero | value(examples2(ID, confs, "configuration"))) <~ sep) ?? Omitted 117 | } 118 | 119 | // New configuration parser that's able to parse configuration ident trailed by slash. 120 | private[sbt] def configIdent(confs: Set[String], idents: Set[String]): Parser[ParsedAxis[String]] = 121 | { 122 | val oldSep: Parser[Char] = ':' 123 | val sep: Parser[Unit] = spacedSlash !!! "Expected '/'" 124 | token( 125 | ((GlobalString ^^^ ParsedZero) <~ oldSep) 126 | | ((GlobalString ^^^ ParsedZero) <~ sep) 127 | | ((ZeroIdent ^^^ ParsedZero) <~ sep) 128 | | (value(examples2(ID, confs, "configuration")) <~ oldSep) 129 | | (value(examples2(CapitalizedID, idents, "configuration ident") map { SlashScope.unguessConfigIdent }) <~ sep) 130 | ) ?? Omitted 131 | } 132 | 133 | def configs(explicit: ParsedAxis[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference], index: KeyIndex): Seq[Option[String]] = 134 | explicit match { 135 | case Omitted => None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(nonEmptyConfig(index, proj)) 136 | case ParsedZero => None :: Nil 137 | case ParsedGlobal => None :: Nil 138 | case pv: ParsedValue[x] => Some(pv.value) :: Nil 139 | } 140 | 141 | def taskAxis(d: Option[String], tasks: Set[AttributeKey[_]], allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] = 142 | { 143 | val taskSeq = tasks.toSeq 144 | def taskKeys(f: AttributeKey[_] => String): Seq[(String, AttributeKey[_])] = taskSeq.map(key => (f(key), key)) 145 | val normKeys = taskKeys(_.label) 146 | val valid = allKnown ++ normKeys ++ taskKeys(_.rawLabel) 147 | val suggested = normKeys.map(_._1).toSet 148 | val keyP = filterStrings(examples2(ID, suggested, "key"), valid.keySet, "key") map valid 149 | (token(value(keyP) | GlobalString ^^^ ParsedZero | ZeroIdent ^^^ ParsedZero) <~ (token("::".id) | spacedSlash)) ?? Omitted 150 | } 151 | 152 | def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] = 153 | task match { 154 | case ParsedZero | ParsedGlobal | Omitted => None 155 | case t: ParsedValue[AttributeKey[_]] @unchecked => Some(t.value) 156 | } 157 | 158 | def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] = 159 | { 160 | val global = token(GlobalString ~ spacedSlash) ^^^ ParsedZero 161 | val zeroIdent = token(ZeroIdent ~ spacedSlash) ^^^ ParsedZero 162 | val thisBuildIdent = value(token(ThisBuildIdent ~ spacedSlash) ^^^ BuildRef(currentBuild)) 163 | val trailing = spacedSlash !!! "Expected '/' (if selecting a project)" 164 | global | zeroIdent | thisBuildIdent | 165 | value(resolvedReferenceIdent(index, currentBuild, trailing)) | 166 | value(resolvedReference(index, currentBuild, trailing)) 167 | } 168 | 169 | private def resolvedReferenceIdent(index: KeyIndex, currentBuild: URI, trailing: Parser[_]): Parser[ResolvedReference] = { 170 | def projectID(uri: URI) = token(DQuoteChar ~> examplesStrict(ID, index projects uri, "project ID") <~ DQuoteChar <~ OptSpace <~ ")" <~ trailing) 171 | def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) } 172 | 173 | val uris = index.buildURIs 174 | val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri)) 175 | 176 | val buildRef = token("ProjectRef(" ~> OptSpace ~> "uri(" ~> OptSpace ~> DQuoteChar ~> 177 | resolvedURI <~ DQuoteChar <~ OptSpace <~ ")" <~ spacedComma) 178 | buildRef flatMap { uri => 179 | projectRef(uri) 180 | } 181 | } 182 | 183 | def resolvedReference(index: KeyIndex, currentBuild: URI, trailing: Parser[_]): Parser[ResolvedReference] = 184 | { 185 | def projectID(uri: URI) = token(examplesStrict(ID, index projects uri, "project ID") <~ trailing) 186 | def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) } 187 | 188 | val uris = index.buildURIs 189 | val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri)) 190 | val buildRef = token('{' ~> resolvedURI <~ '}').? 191 | 192 | buildRef flatMap { 193 | case None => projectRef(currentBuild) 194 | case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri)) 195 | } 196 | } 197 | def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] = 198 | projectRef(index, current.build) ?? Omitted 199 | def resolveProject(parsed: ParsedAxis[ResolvedReference], current: ProjectRef): Option[ResolvedReference] = 200 | parsed match { 201 | case Omitted => Some(current) 202 | case ParsedGlobal | ParsedZero => None 203 | case pv: ParsedValue[rr] => Some(pv.value) 204 | } 205 | 206 | // borrowed from sbt 207 | private[this] final class ActAction 208 | private[this] final val ShowAction, MultiAction, SingleAction = new ActAction 209 | private[this] def actionParser: Parser[ActAction] = 210 | token( 211 | ((ShowCommand ^^^ ShowAction) | 212 | (MultiTaskCommand ^^^ MultiAction)) <~ Space 213 | ) ?? SingleAction 214 | 215 | def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state) 216 | def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef) 217 | def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] = 218 | scopedKey(structure.index.keyIndex, currentRef, structure.extra.configurationsForAxis, structure.index.keyMap, structure.data) 219 | 220 | // this does not take aggregation into account 221 | def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], 222 | keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ScopedKey[_]] = 223 | scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key) 224 | 225 | // borrowed from sbt 226 | type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T }] 227 | private def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state) 228 | private def aggregatedKeyParser(extracted: Extracted): KeysParser = aggregatedKeyParser(extracted.structure, extracted.currentRef) 229 | private def aggregatedKeyParser(structure: BuildStructure, currentRef: ProjectRef): KeysParser = 230 | scopedKeyAggregated(currentRef, structure.extra.configurationsForAxis, structure) 231 | def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(Project extract state)(keys) 232 | def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(extracted.structure)(keys) 233 | def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] = 234 | keys.flatMap { key => 235 | getValue(structure.data, key.scope, key.key) map { value => KeyValue(key, value) } 236 | } 237 | private[this] def anyKeyValues(structure: BuildStructure, keys: Seq[ScopedKey[_]]): Seq[KeyValue[_]] = 238 | keys.flatMap { key => 239 | getValue(structure.data, key.scope, key.key) map { value => KeyValue(key, value) } 240 | } 241 | 242 | private[this] def getValue[T](data: Settings[Scope], scope: Scope, key: AttributeKey[T]): Option[T] = 243 | if (java.lang.Boolean.getBoolean("sbt.cli.nodelegation")) data.getDirect(scope, key) else data.get(scope, key) 244 | 245 | // borrowed from sbt 246 | private def requireSession[T](s: State, p: => Parser[T]): Parser[T] = 247 | if (s get sessionSettings isEmpty) failure("No project loaded") 248 | else p 249 | 250 | sealed trait ParsedAxis[+T] { 251 | final def isExplicit = this != Omitted 252 | } 253 | final object ParsedZero extends ParsedAxis[Nothing] 254 | final object ParsedGlobal extends ParsedAxis[Nothing] 255 | final object Omitted extends ParsedAxis[Nothing] 256 | final class ParsedValue[T](val value: T) extends ParsedAxis[T] 257 | def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v => new ParsedValue(v) } 258 | } 259 | -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashScope.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | object SlashScope { 5 | 6 | private[sbt] val configIdents: Map[String, String] = 7 | Map( 8 | "it" -> "IntegrationTest", 9 | "scala-tool" -> "ScalaTool", 10 | "plugin" -> "CompilerPlugin" 11 | ) 12 | private[sbt] val configIdentsInverse: Map[String, String] = 13 | configIdents map { _.swap } 14 | 15 | private[sbt] def guessConfigIdent(conf: String): String = 16 | configIdents.applyOrElse(conf, (x: String) => x.capitalize) 17 | 18 | private[sbt] def unguessConfigIdent(conf: String): String = 19 | configIdentsInverse.applyOrElse(conf, (x: String) => 20 | x.take(1).toLowerCase + x.drop(1) 21 | ) 22 | 23 | def displayConfig(config: ConfigKey): String = guessConfigIdent(config.name) + "/" 24 | 25 | def display(scope: Scope, sep: String): String = displayMasked(scope, sep, showProject, ScopeMask()) 26 | 27 | def display(scope: Scope, sep: String, showProject: Reference => String): String = displayMasked(scope, sep, showProject, ScopeMask()) 28 | 29 | def displayMasked(scope: Scope, sep: String, showProject: Reference => String, mask: ScopeMask): String = 30 | displayMasked(scope, sep, showProject, mask, false) 31 | 32 | /** 33 | * unified / style introduced in sbt 0.13.16 / sbt 1.0 34 | * By defeault, sbt will no longer display the Zero-config, 35 | * so `name` will render as `name` as opposed to `{uri}proj/Zero/name`. 36 | * Technically speaking an unspecified configuration axis defaults to 37 | * the scope delegation (first configuration defining the key, then Zero). 38 | */ 39 | def displayMasked(scope: Scope, sep: String, showProject: Reference => String, mask: ScopeMask, showZeroConfig: Boolean): String = 40 | { 41 | import scope.{ config, extra } 42 | val zeroConfig = if (showZeroConfig) "Zero/" else "" 43 | val configPrefix = config.foldStrict(displayConfig, zeroConfig, "./") 44 | val taskPrefix = scope.task.foldStrict(_.label + "/", "", "./") 45 | val extras = extra.foldStrict(_.entries.map(_.toString).toList, Nil, Nil) 46 | val postfix = if (extras.isEmpty) "" else extras.mkString("(", ", ", ")") 47 | if (scope == GlobalScope) "Global/" + sep + postfix 48 | else mask.concatShow(projectPrefix(scope.project, showProject), configPrefix, taskPrefix, sep, postfix) 49 | } 50 | 51 | def showProject = (ref: Reference) => Reference.display(ref) + "/" 52 | 53 | def projectPrefix(project: ScopeAxis[Reference], show: Reference => String = showProject): String = 54 | project.foldStrict(show, "Zero/", "./") 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashSyntax.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | import java.io.File 5 | import Scoped.{ RichFileSetting, RichFilesSetting } 6 | 7 | /** 8 | * ScopeSyntax implements the slash syntax to scope keys for build.sbt DSL. 9 | * The implicits are set up such that the order that the scope components 10 | * must appear in the order of the project axis, the configuration axis, and 11 | * the task axis. This ordering is the same as the shell syntax. 12 | * 13 | * @example 14 | * {{{ 15 | * Global / cancelable := true 16 | * ThisBuild / scalaVersion := "2.12.2" 17 | * Test / test := () 18 | * console / scalacOptions += "-deprecation" 19 | * Compile / console / scalacOptions += "-Ywarn-numeric-widen" 20 | * projA / Compile / console / scalacOptions += "-feature" 21 | * Zero / Zero / name := "foo" 22 | * }}} 23 | */ 24 | trait SlashSyntax { 25 | import SlashSyntax._ 26 | 27 | implicit def sbtSlashSyntaxRichReference(r: Reference): RichReference = 28 | new RichReference(Scope(Select(r), This, This, This)) 29 | 30 | implicit def sbtSlashSyntaxRichProject(p: Project): RichReference = 31 | new RichReference(Scope(Select(p), This, This, This)) 32 | 33 | implicit def sbtSlashSyntaxRichConfiguration(c: Configuration): RichConfiguration = 34 | new RichConfiguration(Scope(This, Select(c), This, This)) 35 | 36 | implicit def sbtSlashSyntaxRichScope(s: Scope): RichScope = 37 | new RichScope(s) 38 | 39 | implicit def sbtSlashSyntaxRichScopeFromScoped(t: Scoped): RichScope = 40 | new RichScope(t.scope.copy(task = Select(t.key))) 41 | 42 | implicit def sbtSlashSyntaxRichScopeAxis(a: ScopeAxis[Reference]): RichScopeAxis = 43 | new RichScopeAxis(a) 44 | 45 | // This is for sbt 0.13 compat. Remove for sbt 1.0. 46 | implicit def sbtSlashSyntaxRichFileSetting(s: SettingKey[File]): RichFileSetting = 47 | new RichFileSetting(s) 48 | 49 | // This is for sbt 0.13 compat. Remove for sbt 1.0. 50 | implicit def sbtSlashSyntaxRichFilesSetting(s: SettingKey[Seq[File]]): RichFilesSetting = 51 | new RichFilesSetting(s) 52 | } 53 | 54 | object SlashSyntax { 55 | /** RichReference wraps a project to provide the `/` operator for scoping. */ 56 | final class RichReference(scope: Scope) { 57 | def /(c: ConfigKey): RichConfiguration = new RichConfiguration(scope in c) 58 | def /[A](key: SettingKey[A]): SettingKey[A] = key in scope 59 | def /[A](key: TaskKey[A]): TaskKey[A] = key in scope 60 | def /[A](key: InputKey[A]): InputKey[A] = key in scope 61 | } 62 | 63 | /** RichConfiguration wraps a configuration to provide the `/` operator for scoping. */ 64 | final class RichConfiguration(scope: Scope) { 65 | def /[A](key: SettingKey[A]): SettingKey[A] = key in scope 66 | def /[A](key: TaskKey[A]): TaskKey[A] = key in scope 67 | def /[A](key: InputKey[A]): InputKey[A] = key in scope 68 | } 69 | 70 | /** RichScope wraps a general scope to provide the `/` operator for scoping. */ 71 | final class RichScope(scope: Scope) { 72 | def /[A](key: SettingKey[A]): SettingKey[A] = key in scope 73 | def /[A](key: TaskKey[A]): TaskKey[A] = key in scope 74 | def /[A](key: InputKey[A]): InputKey[A] = key in scope 75 | } 76 | 77 | /** RichScopeAxis wraps a project axis to provide the `/` operator to `Zero` for scoping. */ 78 | final class RichScopeAxis(a: ScopeAxis[Reference]) { 79 | private[this] def toScope: Scope = Scope(a, This, This, This) 80 | 81 | def /(c: ConfigKey): RichConfiguration = new RichConfiguration(toScope in c) 82 | 83 | // This is for handling `Zero / Zero / name`. 84 | def /(configAxis: ScopeAxis[ConfigKey]): RichConfiguration = new RichConfiguration(toScope.copy(config = configAxis)) 85 | 86 | def /[A](key: SettingKey[A]): SettingKey[A] = key in toScope 87 | def /[A](key: TaskKey[A]): TaskKey[A] = key in toScope 88 | def /[A](key: InputKey[A]): InputKey[A] = key in toScope 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/scala/sbt/sbtslash/SlashSyntaxPlugin.scala: -------------------------------------------------------------------------------- 1 | package sbt 2 | package sbtslash 3 | 4 | import sbt._ 5 | import Keys._ 6 | import CommandStrings.InspectCommand 7 | 8 | object SlashSyntaxPlugin extends AutoPlugin { 9 | override def requires = plugins.JvmPlugin 10 | override def trigger = allRequirements 11 | 12 | override lazy val globalSettings: Seq[Def.Setting[_]] = slashSettings 13 | 14 | object autoImport extends SlashSyntax { 15 | val Zero = Global 16 | } 17 | 18 | lazy val slashSettings: Seq[Def.Setting[_]] = Vector( 19 | onLoad := { s => 20 | val ks0 = s.definedCommands 21 | val ks = ks0 map { 22 | case c: ArbitraryCommand => 23 | val h = c.help(s) 24 | h.brief.toList match { 25 | case ("show ", _) :: xs => SlashParser.act 26 | case _ => c 27 | } 28 | case c: SimpleCommand => 29 | if (c.name == InspectCommand) SlashInspect.inspect 30 | else c 31 | case x => x 32 | } 33 | s.copy(definedCommands = ks) 34 | } 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/sbt-test/slash/unified/build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")) 2 | .settings( 3 | Global / cancelable := true, 4 | ThisBuild / scalaVersion := "2.11.11", 5 | Test / test := (), 6 | console / scalacOptions += "-deprecation", 7 | Compile / console / scalacOptions += "-Ywarn-numeric-widen", 8 | projA / Compile / console / scalacOptions += "-feature", 9 | Zero / Zero / name := "foo" 10 | ) 11 | 12 | lazy val projA = (project in file("a")) 13 | -------------------------------------------------------------------------------- /src/sbt-test/slash/unified/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | { 2 | val pluginVersion = System.getProperty("plugin.version") 3 | if(pluginVersion == null) 4 | throw new RuntimeException("""|The system property 'plugin.version' is not defined. 5 | |Specify this property using the scriptedLaunchOpts -D.""".stripMargin) 6 | else addSbtPlugin("com.eed3si9n" % "sbt-slash" % pluginVersion) 7 | } 8 | -------------------------------------------------------------------------------- /src/sbt-test/slash/unified/test: -------------------------------------------------------------------------------- 1 | > inspect tree Test/compile 2 | 3 | > root/Compile/compile 4 | 5 | # "Global" now works in shell. optional whitespace arond / 6 | > show Global / cancelable 7 | 8 | # "ThisBuild" now works in shell. optional whitespace arond / 9 | > show ThisBuild / scalaVersion 10 | 11 | > show Test / test 12 | 13 | > show root / Compile / compile / scalacOptions 14 | 15 | > show Zero / Zero / name 16 | --------------------------------------------------------------------------------