From e498b9bd3a50f670ec98712cf561913385f6791d Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Thu, 24 Mar 2011 21:25:57 -0400 Subject: [PATCH] tab completion fixes and cleanup --- main/Act.scala | 70 ++++++++++++++++++++++++++++---- main/Build.scala | 5 ++- main/Command.scala | 8 ++-- main/Defaults.scala | 4 +- main/KeyIndex.scala | 3 ++ main/Main.scala | 2 +- main/Scope.scala | 5 ++- util/collection/Attributes.scala | 8 ++++ 8 files changed, 86 insertions(+), 19 deletions(-) diff --git a/main/Act.scala b/main/Act.scala index 43ab23a6f..e41cdfc61 100644 --- a/main/Act.scala +++ b/main/Act.scala @@ -12,35 +12,48 @@ package sbt object Act { - // this does not take delegation into account + val GlobalString = "*" + + // this does not take aggregation into account def scopedKey(index: KeyIndex, current: ProjectRef, defaultConfig: ProjectRef => Option[String], keyMap: Map[String, AttributeKey[_]]): Parser[ScopedKey[_]] = { for { proj <- optProjectRef(index, current) confAmb <- config( index configs proj ) - (key, conf) <- key(index, proj, configs(confAmb, defaultConfig, proj), keyMap) } - yield - ScopedKey( Scope( Select(proj), toAxis(conf map ConfigKey.apply, Global), Global, Global), key ) + keyConf <- key(index, proj, configs(confAmb, defaultConfig, proj), keyMap) + taskExtra <- taskExtrasParser(keyMap, IMap.empty) } + yield { + val (key, conf) = keyConf + val (task, extra) = taskExtra + ScopedKey( Scope( Select(proj), toAxis(conf map ConfigKey.apply, Global), task, extra), key ) + } } def examplesStrict(p: Parser[String], exs: Set[String]): Parser[String] = p examples exs filter exs - + + 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 defaultConfig(data: Settings[Scope])(project: ProjectRef): Option[String] = thisProject in project get data flatMap( _.configurations.headOption.map(_.name)) def config(confs: Set[String]): Parser[Option[String]] = - token( examplesStrict(ID, confs) <~ ':' ).? + token( (examplesStrict(ID, confs) | GlobalString) <~ ':' ).? def configs(explicit: Option[String], defaultConfig: ProjectRef => Option[String], proj: ProjectRef): List[Option[String]] = - if(explicit.isDefined) explicit :: Nil else None :: defaultConfig(proj) :: Nil + explicit match + { + case None => None :: defaultConfig(proj) :: Nil + case Some(GlobalString) => None :: Nil + case Some(_) => explicit :: Nil + } + def key(index: KeyIndex, proj: ProjectRef, confs: Seq[Option[String]], keyMap: Map[String,AttributeKey[_]]): Parser[(AttributeKey[_], Option[String])] = { val confMap = confs map { conf => (conf, index.keys(proj, conf)) } toMap; val allKeys = (Set.empty[String] /: confMap.values)(_ ++ _) - val filteredKeys = allKeys.filter(Command.validID) - token(ID examples filteredKeys).flatMap { keyString => + token(ID examples allKeys).flatMap { keyString => val conf = confMap.flatMap { case (key, value) => if(value contains keyString) key :: Nil else Nil } headOption; getKey(keyMap, keyString, k => (k, conf.flatMap(identity))) } @@ -51,6 +64,45 @@ object Act case None => failure("Invalid key: " + keyString) } + val spacedComma = token(OptSpace ~ ',' ~ OptSpace) + + def taskExtrasParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[(ScopeAxis[AttributeKey[_]], ScopeAxis[AttributeMap])] = + { + val extras = extrasParser(knownKeys, knownValues) + val taskAndExtra = + optionalAxis(taskAxisParser(knownKeys), Global) flatMap { taskAxis => + if(taskAxis.isSelect) + optionalAxis(spacedComma ~> extras, Global) map { x => (taskAxis, x) } + else + extras map { x => (taskAxis, Select(x)) } + } + val base = token('(') ~> taskAndExtra <~ token(')') + base ?? ( (Global, Global) ) + } + + def taskAxisParser(knownKeys: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] = + token("for" ~ Space) ~> knownIDParser(knownKeys) + + 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("") + 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) <~ token(':' ~ OptSpace) + keyp flatMap { case key: AttributeKey[t] => + val valueMap: Map[String,t] = knownValues(key).map( v => (v.toString, v)).toMap + knownIDParser(valueMap) map { value => AttributeEntry(key, value) } + } + } + def knownIDParser[T](knownKeys: Map[String, T]): Parser[T] = + token(examplesStrict(ID, knownKeys.keys.toSet)) map knownKeys + def projectRef(index: KeyIndex, currentBuild: URI): Parser[ProjectRef] = { val uris = index.buildURIs diff --git a/main/Build.scala b/main/Build.scala index a581fb616..c148698ce 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -9,6 +9,7 @@ package sbt import classpath.ClasspathUtilities import scala.annotation.tailrec import collection.mutable + import complete.DefaultParsers.validID import Compiler.{Compilers,Inputs} import Project.{inScope, ScopedKey, ScopeLocal, Setting} import Keys.{appConfiguration, baseDirectory, configuration, streams, thisProject, thisProjectRef} @@ -225,7 +226,7 @@ object Index def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] = { // AttributeEntry + the checked type test 'value: Task[_]' ensures that the cast is correct. - // (scalac couldn't determine that 'key' is of type AttributeKey[Task[_]] on its own and a type match didn't still required the cast) + // (scalac couldn't determine that 'key' is of type AttributeKey[Task[_]] on its own and a type match still required the cast) val pairs = for( scope <- data.scopes; AttributeEntry(key, value: Task[_]) <- data.data(scope).entries ) yield (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) // unclear why this cast is needed even with a type test in the above filter pairs.toMap[Task[_], ScopedKey[Task[_]]] @@ -235,7 +236,7 @@ object Index val multiMap = settings.data.values.flatMap(_.keys).toList.distinct.groupBy(_.label) val duplicates = multiMap collect { case (k, x1 :: x2 :: _) => k } if(duplicates.isEmpty) - multiMap.mapValues(_.head) + multiMap.collect { case (k, v) if validID(k) => (k, v.head) } toMap; else error(duplicates.mkString("AttributeKey ID collisions detected for '", "', '", "'")) } diff --git a/main/Command.scala b/main/Command.scala index 698ce9967..3b5567062 100644 --- a/main/Command.scala +++ b/main/Command.scala @@ -24,7 +24,7 @@ private[sbt] final class ArbitraryCommand(val parser: State => Parser[() => Stat object Command { - def pointerSpace(s: String, i: Int): String = (s take i) map { case '\t' => '\t'; case _ => ' ' } mkString; + def pointerSpace(s: String, i: Int): String = (s take i) map { case '\t' => '\t'; case _ => ' ' } mkString; import DefaultParsers._ @@ -54,8 +54,7 @@ object Command def custom(parser: State => Parser[() => State], help: Seq[Help] = Nil): Command = new ArbitraryCommand(parser, help, AttributeMap.empty) - def validID(name: String) = - Parser(OpOrID)(name).resultEmpty.isDefined + def validID(name: String) = DefaultParsers.matches(OpOrID, name) def applyEffect[T](p: Parser[T])(f: T => State): Parser[() => State] = p map { t => () => f(t) } @@ -93,7 +92,8 @@ object Command def commandError(command: String, msg: String, index: Int): String = { val (line, modIndex) = extractLine(command, index) - msg + "\n" + line + "\n" + pointerSpace(msg, modIndex) + "^" + val point = pointerSpace(command, modIndex) + msg + "\n" + line + "\n" + point + "^" } def extractLine(s: String, i: Int): (String, Int) = { diff --git a/main/Defaults.scala b/main/Defaults.scala index 4c74d1e9e..123b362ff 100755 --- a/main/Defaults.scala +++ b/main/Defaults.scala @@ -356,10 +356,10 @@ object Defaults { state => import DefaultParsers._ def distinctParser(exs: Set[String]): Parser[Seq[String]] = - token(Space ~> NotSpace.examples(exs)).flatMap(ex => distinctParser(exs - ex).map(ex +: _)) ?? Nil + token(Space ~> (NotSpace - "--").examples(exs) ).flatMap(ex => distinctParser(exs - ex).map(ex +: _)) ?? Nil val tests = savedLines(state, resolved, definedTests) val selectTests = distinctParser(tests.toSet) // todo: proper IDs - val options = (Space ~> "--" ~> spaceDelimited("")) ?? Nil + val options = (token(Space ~> "--") ~> spaceDelimited("