mirror of https://github.com/sbt/sbt.git
273 lines
12 KiB
Scala
273 lines
12 KiB
Scala
/* sbt -- Simple Build Tool
|
|
* Copyright 2011 Mark Harrah
|
|
*/
|
|
package sbt
|
|
|
|
import Project.{ScopedKey, showContextKey}
|
|
import Keys.{sessionSettings, thisProject}
|
|
import Load.BuildStructure
|
|
import complete.{DefaultParsers, Parser}
|
|
import Aggregation.{KeyValue,Values}
|
|
import DefaultParsers._
|
|
import Types.idFun
|
|
import java.net.URI
|
|
import CommandStrings.ShowCommand
|
|
|
|
final class ParsedKey(val key: ScopedKey[_], val mask: ScopeMask)
|
|
object Act
|
|
{
|
|
val GlobalString = "*"
|
|
|
|
// this does not take aggregation into account
|
|
def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String],
|
|
keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ScopedKey[_]] =
|
|
scopedKeySelected(index, current, defaultConfigs, keyMap, data).map(_.key)
|
|
|
|
// the index should be an aggregated index for proper tab completion
|
|
def scopedKeyAggregated(current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], structure: BuildStructure): KeysParser =
|
|
for(selected <- scopedKeySelected(structure.index.aggregateKeyIndex, current, defaultConfigs, structure.index.keyMap, structure.data) ) yield
|
|
Aggregation.aggregate(selected.key, selected.mask, structure.extra)
|
|
|
|
def scopedKeySelected(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String],
|
|
keyMap: Map[String, AttributeKey[_]], data: Settings[Scope]): Parser[ParsedKey] =
|
|
scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices =>
|
|
select(choices, data)( Project.showRelativeKey(current, index.buildURIs.size > 1) )
|
|
}
|
|
|
|
def scopedKeyFull(index: KeyIndex, current: ProjectRef, defaultConfigs: Option[ResolvedReference] => Seq[String], keyMap: Map[String, AttributeKey[_]]): Parser[Seq[Parser[ParsedKey]]] =
|
|
{
|
|
def taskKeyExtra(proj: Option[ResolvedReference], confAmb: ParsedAxis[String], baseMask: ScopeMask): Seq[Parser[ParsedKey]] =
|
|
for {
|
|
conf <- configs(confAmb, defaultConfigs, proj, index)
|
|
} yield for {
|
|
taskAmb <- taskAxis(conf, index.tasks(proj, conf), keyMap)
|
|
task = resolveTask(taskAmb)
|
|
key <- key(index, proj, conf, task, keyMap)
|
|
extra <- extraAxis(keyMap, IMap.empty)
|
|
} yield {
|
|
val mask = baseMask.copy(task = taskAmb.isExplicit, extra = true)
|
|
new ParsedKey( makeScopedKey( proj, conf, task, extra, key ), mask)
|
|
}
|
|
|
|
for {
|
|
rawProject <- optProjectRef(index, current)
|
|
proj = resolveProject(rawProject, current)
|
|
confAmb <- config( index configs proj )
|
|
partialMask = ScopeMask(rawProject.isExplicit, confAmb.isExplicit, false, false)
|
|
} yield
|
|
taskKeyExtra(proj, confAmb, partialMask)
|
|
}
|
|
def makeScopedKey(proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], extra: ScopeAxis[AttributeMap], key: AttributeKey[_]): ScopedKey[_] =
|
|
ScopedKey( Scope( toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), toAxis(task, Global), extra), key )
|
|
|
|
def select(allKeys: Seq[Parser[ParsedKey]], data: Settings[Scope])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
|
|
seq(allKeys) flatMap { ss =>
|
|
val default = ss.headOption match {
|
|
case None => noValidKeys
|
|
case Some(x) => success(x)
|
|
}
|
|
selectFromValid(ss filter isValid(data), default)
|
|
}
|
|
def selectFromValid(ss: Seq[ParsedKey], default: Parser[ParsedKey])(implicit show: Show[ScopedKey[_]]): Parser[ParsedKey] =
|
|
selectByTask(selectByConfig(ss)) match
|
|
{
|
|
case Seq() => default
|
|
case Seq(single) => success(single)
|
|
case multi => failure("Ambiguous keys: " + showAmbiguous(keys(multi)))
|
|
}
|
|
private[this] def keys(ss: Seq[ParsedKey]): Seq[ScopedKey[_]] = ss.map(_.key)
|
|
def selectByConfig(ss: Seq[ParsedKey]): Seq[ParsedKey] =
|
|
ss match
|
|
{
|
|
case Seq() => Nil
|
|
case Seq(x, tail @ _*) => // select the first configuration containing a valid key
|
|
tail.takeWhile(_.key.scope.config == x.key.scope.config) match
|
|
{
|
|
case Seq() => x :: Nil
|
|
case xs => x +: xs
|
|
}
|
|
}
|
|
def selectByTask(ss: Seq[ParsedKey]): Seq[ParsedKey] =
|
|
{
|
|
val (selects, globals) = ss.partition(_.key.scope.task.isSelect)
|
|
if(globals.nonEmpty) globals else selects
|
|
}
|
|
|
|
def noValidKeys = failure("No such key.")
|
|
|
|
def showAmbiguous(keys: Seq[ScopedKey[_]])(implicit show: Show[ScopedKey[_]]): String =
|
|
keys.take(3).map(x => show(x)).mkString("", ", ", if(keys.size > 3) ", ..." else "")
|
|
|
|
def isValid(data: Settings[Scope])(parsed: ParsedKey): Boolean =
|
|
{
|
|
val key = parsed.key
|
|
data.definingScope(key.scope, key.key) == Some(key.scope)
|
|
}
|
|
|
|
def examples(p: Parser[String], exs: Set[String], label: String): Parser[String] =
|
|
p !!! ("Expected " + label) examples exs
|
|
def examplesStrict(p: Parser[String], exs: Set[String], label: String): Parser[String] =
|
|
filterStrings(examples(p, exs, label), exs, label)
|
|
|
|
def optionalAxis[T](p: Parser[T], ifNone: ScopeAxis[T]): Parser[ScopeAxis[T]] =
|
|
p.? map { opt => toAxis(opt, ifNone) }
|
|
def toAxis[T](opt: Option[T], ifNone: ScopeAxis[T]): ScopeAxis[T] =
|
|
opt match { case Some(t) => Select(t); case None => ifNone }
|
|
|
|
def config(confs: Set[String]): Parser[ParsedAxis[String]] =
|
|
token( (GlobalString ^^^ ParsedGlobal | value(examples(ID, confs, "configuration")) ) <~ ':' ) ?? Omitted
|
|
|
|
def configs(explicit: ParsedAxis[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference], index: KeyIndex): Seq[Option[String]] =
|
|
explicit match
|
|
{
|
|
case Omitted => None +: defaultConfigurations(proj, index, defaultConfigs).flatMap(nonEmptyConfig(index, proj))
|
|
case ParsedGlobal => None :: Nil
|
|
case pv: ParsedValue[String] => Some(pv.value) :: Nil
|
|
}
|
|
def defaultConfigurations(proj: Option[ResolvedReference], index: KeyIndex, defaultConfigs: Option[ResolvedReference] => Seq[String]): Seq[String] =
|
|
if(index exists proj) defaultConfigs(proj) else Nil
|
|
def nonEmptyConfig(index: KeyIndex, proj: Option[ResolvedReference]): String => Seq[Option[String]] = config =>
|
|
if(index.isEmpty(proj, Some(config))) Nil else Some(config) :: Nil
|
|
|
|
def key(index: KeyIndex, proj: Option[ResolvedReference], conf: Option[String], task: Option[AttributeKey[_]], keyMap: Map[String,AttributeKey[_]]): Parser[AttributeKey[_]] =
|
|
{
|
|
def keyParser(keys: Set[String]): Parser[AttributeKey[_]] =
|
|
token(ID !!! "Expected key" examples keys) flatMap { keyString=>
|
|
getKey(keyMap, keyString, idFun)
|
|
}
|
|
keyParser(index.keys(proj, conf, task))
|
|
}
|
|
|
|
def getKey[T](keyMap: Map[String,AttributeKey[_]], keyString: String, f: AttributeKey[_] => T): Parser[T] =
|
|
keyMap.get(keyString) match {
|
|
case Some(k) => success(f(k))
|
|
case None => failure(Command.invalidValue("key", keyMap.keys)(keyString))
|
|
}
|
|
|
|
val spacedComma = token(OptSpace ~ ',' ~ OptSpace)
|
|
|
|
def extraAxis(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[ScopeAxis[AttributeMap]] =
|
|
{
|
|
val extrasP = extrasParser(knownKeys, knownValues)
|
|
val extras = token('(', hide = _ == 1 && knownValues.isEmpty) ~> extrasP <~ token(')')
|
|
optionalAxis(extras, Global)
|
|
}
|
|
|
|
def taskAxis(d: Option[String], tasks: Set[AttributeKey[_]], allKnown: Map[String, AttributeKey[_]]): Parser[ParsedAxis[AttributeKey[_]]] =
|
|
{
|
|
val knownKeys: Map[String, AttributeKey[_]] = tasks.toSeq.map(key => (key.label, key)).toMap
|
|
val valid = allKnown ++ knownKeys
|
|
val suggested = knownKeys.keySet
|
|
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid
|
|
(token(value(keyP) | GlobalString ^^^ ParsedGlobal ) <~ token("::".id) ) ?? Omitted
|
|
}
|
|
def resolveTask(task: ParsedAxis[AttributeKey[_]]): Option[AttributeKey[_]] =
|
|
task match
|
|
{
|
|
case ParsedGlobal | Omitted => None
|
|
case t: ParsedValue[AttributeKey[_]] => Some(t.value)
|
|
}
|
|
|
|
def filterStrings(base: Parser[String], valid: Set[String], label: String): Parser[String] =
|
|
base.filter(valid, Command.invalidValue(label, valid))
|
|
|
|
def extrasParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeMap] =
|
|
{
|
|
val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists(!_.isEmpty) }
|
|
if(validKeys.isEmpty)
|
|
failure("No valid extra keys.")
|
|
else
|
|
rep1sep( extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply
|
|
}
|
|
|
|
def extraParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeEntry[_]] =
|
|
{
|
|
val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace)
|
|
keyp flatMap { case key: AttributeKey[t] =>
|
|
val valueMap: Map[String,t] = knownValues(key).map( v => (v.toString, v)).toMap
|
|
knownIDParser(valueMap, "extra value") map { value => AttributeEntry(key, value) }
|
|
}
|
|
}
|
|
def knownIDParser[T](knownKeys: Map[String, T], label: String): Parser[T] =
|
|
token(examplesStrict(ID, knownKeys.keys.toSet, label)) map knownKeys
|
|
|
|
def projectRef(index: KeyIndex, currentBuild: URI): Parser[ParsedAxis[ResolvedReference]] =
|
|
{
|
|
val global = token(GlobalString ~ '/') ^^^ ParsedGlobal
|
|
global | value(resolvedReference(index, currentBuild, '/'))
|
|
}
|
|
def resolvedReference(index: KeyIndex, currentBuild: URI, trailing: Parser[_]): Parser[ResolvedReference] =
|
|
{
|
|
def projectID(uri: URI) = token( examples(ID, index projects uri, "project ID") <~ trailing )
|
|
def projectRef(uri: URI) = projectID(uri) map { id => ProjectRef(uri, id) }
|
|
|
|
val uris = index.buildURIs
|
|
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
|
|
val buildRef = token( '{' ~> resolvedURI <~ '}' ).?
|
|
|
|
buildRef flatMap {
|
|
case None => projectRef(currentBuild)
|
|
case Some(uri) => projectRef(uri) | token(trailing ^^^ BuildRef(uri))
|
|
}
|
|
}
|
|
def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[ParsedAxis[ResolvedReference]] =
|
|
projectRef(index, current.build) ?? Omitted
|
|
def resolveProject(parsed: ParsedAxis[ResolvedReference], current: ProjectRef): Option[ResolvedReference] =
|
|
parsed match
|
|
{
|
|
case Omitted => Some(current)
|
|
case ParsedGlobal => None
|
|
case pv: ParsedValue[ResolvedReference] => Some(pv.value)
|
|
}
|
|
|
|
def actParser(s: State): Parser[() => State] = requireSession(s, actParser0(s))
|
|
|
|
private[this] def actParser0(state: State): Parser[() => State] =
|
|
{
|
|
val extracted = Project extract state
|
|
import extracted.{showKey, structure}
|
|
import Aggregation.evaluatingParser
|
|
showParser.flatMap { show =>
|
|
val akp = aggregatedKeyParser(extracted)
|
|
def evaluate(kvs: Seq[ScopedKey[T]] forSome { type T}): Parser[() => State] = evaluatingParser(state, structure, show)( keyValues(structure)(kvs) )
|
|
def reconstruct(arg: String): String = ShowCommand + " " + arg
|
|
if(show)
|
|
( akp ~ (token(Space) ~> matched(akp)).* ) flatMap { case (kvs, tail) =>
|
|
evaluate(kvs) map { f => () => tail.map(reconstruct) ::: f() }
|
|
}
|
|
else
|
|
akp flatMap evaluate
|
|
}
|
|
}
|
|
def showParser = token( (ShowCommand ~ Space) ^^^ true) ?? false
|
|
def scopedKeyParser(state: State): Parser[ScopedKey[_]] = scopedKeyParser(Project extract state)
|
|
def scopedKeyParser(extracted: Extracted): Parser[ScopedKey[_]] = scopedKeyParser(extracted.structure, extracted.currentRef)
|
|
def scopedKeyParser(structure: BuildStructure, currentRef: ProjectRef): Parser[ScopedKey[_]] =
|
|
scopedKey(structure.index.keyIndex, currentRef, structure.extra.configurationsForAxis, structure.index.keyMap, structure.data)
|
|
|
|
type KeysParser = Parser[Seq[ScopedKey[T]] forSome { type T}]
|
|
def aggregatedKeyParser(state: State): KeysParser = aggregatedKeyParser(Project extract state)
|
|
def aggregatedKeyParser(extracted: Extracted): KeysParser = aggregatedKeyParser(extracted.structure, extracted.currentRef)
|
|
def aggregatedKeyParser(structure: BuildStructure, currentRef: ProjectRef): KeysParser =
|
|
scopedKeyAggregated(currentRef, structure.extra.configurationsForAxis, structure)
|
|
|
|
def keyValues[T](state: State)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(Project extract state)(keys)
|
|
def keyValues[T](extracted: Extracted)(keys: Seq[ScopedKey[T]]): Values[T] = keyValues(extracted.structure)(keys)
|
|
def keyValues[T](structure: BuildStructure)(keys: Seq[ScopedKey[T]]): Values[T] =
|
|
keys.flatMap { key =>
|
|
structure.data.get(key.scope, key.key) map { value =>
|
|
KeyValue(key, value)
|
|
}
|
|
}
|
|
|
|
def requireSession[T](s: State, p: => Parser[T]): Parser[T] =
|
|
if(s get sessionSettings isEmpty) failure("No project loaded") else p
|
|
|
|
sealed trait ParsedAxis[+T] {
|
|
final def isExplicit = this != Omitted
|
|
}
|
|
final object ParsedGlobal extends ParsedAxis[Nothing]
|
|
final object Omitted extends ParsedAxis[Nothing]
|
|
final class ParsedValue[T](val value: T) extends ParsedAxis[T]
|
|
def value[T](t: Parser[T]): Parser[ParsedAxis[T]] = t map { v => new ParsedValue(v) }
|
|
} |