mirror of https://github.com/sbt/sbt.git
Documented the new Parsers API a bit. Prepared the new API so that we can port the old ones to the new. Added support for filtering erroneous examples.
This commit is contained in:
parent
b9e37107b2
commit
6a4eb92ee5
|
|
@ -9,18 +9,29 @@ import java.io.File
|
||||||
*/
|
*/
|
||||||
trait ExampleSource
|
trait ExampleSource
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return a (possibly lazy) list of completion example strings. These strings are continuations of user's input. The
|
* @return a (possibly lazy) list of completion example strings. These strings are continuations of user's input. The
|
||||||
* user's input is incremented with calls to [[withAddedPrefix]].
|
* user's input is incremented with calls to [[withAddedPrefix]].
|
||||||
*/
|
*/
|
||||||
def apply(): Iterable[String]
|
def apply(): Iterable[String]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param addedPrefix a string that just typed in by the user.
|
* @param addedPrefix a string that just typed in by the user.
|
||||||
* @return a new source of only those examples that start with the string typed by the user so far (with addition of
|
* @return a new source of only those examples that start with the string typed by the user so far (with addition of
|
||||||
* the just added prefix).
|
* the just added prefix).
|
||||||
*/
|
*/
|
||||||
def withAddedPrefix(addedPrefix: String): ExampleSource
|
def withAddedPrefix(addedPrefix: String): ExampleSource
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param examples the source of examples that will be displayed to the user when they press the TAB key.
|
||||||
|
*/
|
||||||
|
sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSource {
|
||||||
|
override def withAddedPrefix(addedPrefix: String): ExampleSource = FixedSetExamples(examplesWithRemovedPrefix(addedPrefix))
|
||||||
|
|
||||||
|
override def apply(): Iterable[String] = examples
|
||||||
|
|
||||||
|
private def examplesWithRemovedPrefix(prefix: String) = examples.collect { case example if example startsWith prefix => example substring prefix.length }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,21 +40,21 @@ trait ExampleSource
|
||||||
* @param prefix the part of the path already written by the user.
|
* @param prefix the part of the path already written by the user.
|
||||||
*/
|
*/
|
||||||
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
|
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
|
||||||
private val relativizedPrefix: String = "." + File.separator + prefix
|
private val relativizedPrefix: String = "." + File.separator + prefix
|
||||||
|
|
||||||
override def apply(): Iterable[String] = files(base).map(_.toString.substring(relativizedPrefix.length))
|
override def apply(): Iterable[String] = files(base).map(_.toString.substring(relativizedPrefix.length))
|
||||||
|
|
||||||
override def withAddedPrefix(addedPrefix: String): FileExamples = new FileExamples(base, prefix + addedPrefix)
|
override def withAddedPrefix(addedPrefix: String): FileExamples = new FileExamples(base, prefix + addedPrefix)
|
||||||
|
|
||||||
protected def fileStartsWithPrefix(path: File): Boolean = path.toString.startsWith(relativizedPrefix)
|
protected def fileStartsWithPrefix(path: File): Boolean = path.toString.startsWith(relativizedPrefix)
|
||||||
|
|
||||||
protected def directoryStartsWithPrefix(path: File): Boolean = {
|
protected def directoryStartsWithPrefix(path: File): Boolean = {
|
||||||
val pathString = path.toString
|
val pathString = path.toString
|
||||||
pathString.startsWith(relativizedPrefix) || relativizedPrefix.startsWith(pathString)
|
pathString.startsWith(relativizedPrefix) || relativizedPrefix.startsWith(pathString)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected def files(directory: File): Iterable[File] = {
|
protected def files(directory: File): Iterable[File] = {
|
||||||
val (subDirectories, filesOnly) = directory.listFiles().toStream.partition(_.isDirectory)
|
val (subDirectories, filesOnly) = directory.listFiles().toStream.partition(_.isDirectory)
|
||||||
filesOnly.filter(fileStartsWithPrefix) ++ subDirectories.filter(directoryStartsWithPrefix).flatMap(files)
|
filesOnly.filter(fileStartsWithPrefix) ++ subDirectories.filter(directoryStartsWithPrefix).flatMap(files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
package sbt.complete
|
package sbt.complete
|
||||||
|
|
||||||
import Parser._
|
import Parser._
|
||||||
import sbt.Types.{const, left, right, some}
|
import sbt.Types.{left, right, some}
|
||||||
import sbt.Util.{makeList,separate}
|
import sbt.Util.{makeList,separate}
|
||||||
|
|
||||||
/** A String parser that provides semi-automatic tab completion.
|
/** A String parser that provides semi-automatic tab completion.
|
||||||
|
|
@ -87,8 +87,23 @@ sealed trait RichParser[A]
|
||||||
/** Explicitly defines the completions for the original Parser.*/
|
/** Explicitly defines the completions for the original Parser.*/
|
||||||
def examples(s: Set[String], check: Boolean = false): Parser[A]
|
def examples(s: Set[String], check: Boolean = false): Parser[A]
|
||||||
|
|
||||||
/** Explicitly defines the completions for the original Parser.*/
|
/**
|
||||||
def examples(s: ExampleSource, maxNumberOfExamples: Int): Parser[A]
|
* @param exampleSource the source of examples when displaying completions to the user.
|
||||||
|
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
|
||||||
|
* prevent lengthy pauses and avoids bad interactive user experience.
|
||||||
|
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the
|
||||||
|
* given parser). Invalid examples will be filtered out and only valid suggestions will
|
||||||
|
* be displayed.
|
||||||
|
* @return a new parser with a new source of completions.
|
||||||
|
*/
|
||||||
|
def examples(exampleSource: ExampleSource, maxNumberOfExamples: Int, removeInvalidExamples: Boolean): Parser[A]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param exampleSource the source of examples when displaying completions to the user.
|
||||||
|
* @return a new parser with a new source of completions. It displays at most 25 completion examples and does not
|
||||||
|
* remove invalid examples.
|
||||||
|
*/
|
||||||
|
def examples(exampleSource: ExampleSource): Parser[A] = examples(exampleSource, maxNumberOfExamples = 25, removeInvalidExamples = false)
|
||||||
|
|
||||||
/** Converts a Parser returning a Char sequence to a Parser returning a String.*/
|
/** Converts a Parser returning a Char sequence to a Parser returning a String.*/
|
||||||
def string(implicit ev: A <:< Seq[Char]): Parser[String]
|
def string(implicit ev: A <:< Seq[Char]): Parser[String]
|
||||||
|
|
@ -288,7 +303,7 @@ trait ParserMain
|
||||||
def - (o: Parser[_]) = sub(a, o)
|
def - (o: Parser[_]) = sub(a, o)
|
||||||
def examples(s: String*): Parser[A] = examples(s.toSet)
|
def examples(s: String*): Parser[A] = examples(s.toSet)
|
||||||
def examples(s: Set[String], check: Boolean = false): Parser[A] = Parser.examples(a, s, check)
|
def examples(s: Set[String], check: Boolean = false): Parser[A] = Parser.examples(a, s, check)
|
||||||
def examples(s: ExampleSource, maxNumberOfExamples: Int): Parser[A] = Parser.examples(a, s, maxNumberOfExamples)
|
def examples(s: ExampleSource, maxNumberOfExamples: Int, removeInvalidExamples: Boolean): Parser[A] = Parser.examples(a, s, maxNumberOfExamples, removeInvalidExamples)
|
||||||
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
|
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
|
||||||
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
|
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
|
||||||
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
|
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
|
||||||
|
|
@ -434,13 +449,24 @@ trait ParserMain
|
||||||
}
|
}
|
||||||
else a
|
else a
|
||||||
|
|
||||||
def examples[A](a: Parser[A], completions: ExampleSource, maxNumberOfExamples: Int): Parser[A] =
|
/**
|
||||||
|
* @param a the parser to decorate with a source of examples. All validation and parsing is delegated to this parser,
|
||||||
|
* only [[Parser.completions]] is modified.
|
||||||
|
* @param completions the source of examples when displaying completions to the user.
|
||||||
|
* @param maxNumberOfExamples limits the number of examples that the source of examples should return. This can
|
||||||
|
* prevent lengthy pauses and avoids bad interactive user experience.
|
||||||
|
* @param removeInvalidExamples indicates whether completion examples should be checked for validity (against the given parser). An
|
||||||
|
* exception is thrown if the example source contains no valid completion suggestions.
|
||||||
|
* @tparam A the type of values that are returned by the parser.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def examples[A](a: Parser[A], completions: ExampleSource, maxNumberOfExamples: Int, removeInvalidExamples: Boolean): Parser[A] =
|
||||||
if(a.valid) {
|
if(a.valid) {
|
||||||
a.result match
|
a.result match
|
||||||
{
|
{
|
||||||
case Some(av) => success( av )
|
case Some(av) => success( av )
|
||||||
case None =>
|
case None =>
|
||||||
new DynamicExamples(a, completions, maxNumberOfExamples)
|
new DynamicExamples(a, completions, maxNumberOfExamples, removeInvalidExamples)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else a
|
else a
|
||||||
|
|
@ -718,20 +744,31 @@ private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends
|
||||||
Completions(fixed map(f => Completion.suggestion(f)) )
|
Completions(fixed map(f => Completion.suggestion(f)) )
|
||||||
override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")"
|
override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")"
|
||||||
}
|
}
|
||||||
private final class DynamicExamples[T](delegate: Parser[T], exampleSource: ExampleSource, maxNumberOfExamples: Int) extends ValidParser[T]
|
private final class DynamicExamples[T](delegate: Parser[T], exampleSource: ExampleSource, maxNumberOfExamples: Int, removeInvalidExamples: Boolean) extends ValidParser[T]
|
||||||
{
|
{
|
||||||
def derive(c: Char) = examples(delegate derive c, exampleSource.withAddedPrefix(c.toString), maxNumberOfExamples)
|
def derive(c: Char) = examples(delegate derive c, exampleSource.withAddedPrefix(c.toString), maxNumberOfExamples, removeInvalidExamples)
|
||||||
def result = delegate.result
|
def result = delegate.result
|
||||||
lazy val resultEmpty = delegate.resultEmpty
|
lazy val resultEmpty = delegate.resultEmpty
|
||||||
def completions(level: Int) = {
|
def completions(level: Int) = {
|
||||||
if(exampleSource().isEmpty)
|
if(exampleSource().isEmpty)
|
||||||
if(resultEmpty.isValid) Completions.nil else Completions.empty
|
if(resultEmpty.isValid) Completions.nil else Completions.empty
|
||||||
else {
|
else {
|
||||||
val examplesBasedOnTheResult = exampleSource().take(maxNumberOfExamples).toSet
|
val examplesBasedOnTheResult = filteredExamples.take(maxNumberOfExamples).toSet
|
||||||
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
|
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override def toString = "examples(" + delegate + ", " + exampleSource().take(2).toList + ")"
|
override def toString = "examples(" + delegate + ", " + exampleSource().take(2).toList + ")"
|
||||||
|
|
||||||
|
private def filteredExamples: Iterable[String] = {
|
||||||
|
if (removeInvalidExamples)
|
||||||
|
exampleSource().filter(isExampleValid)
|
||||||
|
else
|
||||||
|
exampleSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def isExampleValid(example: String): Boolean = {
|
||||||
|
apply(delegate)(example).resultEmpty.isFailure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]
|
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -128,21 +128,16 @@ trait Parsers
|
||||||
/** Returns true if `c` is an ASCII letter or digit. */
|
/** Returns true if `c` is an ASCII letter or digit. */
|
||||||
def alphanum(c: Char) = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
|
def alphanum(c: Char) = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param base the directory used for completion proposals (when the user presses the TAB key). Only paths under this
|
* @param base the directory used for completion proposals (when the user presses the TAB key). Only paths under this
|
||||||
* directory will be proposed.
|
* directory will be proposed.
|
||||||
* @return the file that was parsed from the input string. The returned path may or may not exist.
|
* @return the file that was parsed from the input string. The returned path may or may not exist.
|
||||||
*/
|
*/
|
||||||
def fileParser(base: File, maxNumberOfExamples: Int): Parser[File] =
|
def fileParser(base: File): Parser[File] =
|
||||||
OptSpace ~> StringBasic
|
OptSpace ~> StringBasic
|
||||||
.examples(new FileExamples(base), maxNumberOfExamples)
|
.examples(new FileExamples(base))
|
||||||
.map(new File(_))
|
.map(new File(_))
|
||||||
|
|
||||||
/**
|
|
||||||
* See the overloaded [[fileParser]] method.
|
|
||||||
*/
|
|
||||||
def fileParser(base: File): Parser[File] = fileParser(base, 25)
|
|
||||||
|
|
||||||
/** Parses a port number. Currently, this accepts any integer and presents a tab completion suggestion of `<port>`. */
|
/** Parses a port number. Currently, this accepts any integer and presents a tab completion suggestion of `<port>`. */
|
||||||
lazy val Port = token(IntBasic, "<port>")
|
lazy val Port = token(IntBasic, "<port>")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue