mirror of https://github.com/sbt/sbt.git
Implemented a file parser. Added SourceOfExamples for lazy example listing (especially useful when lazily searching for files that match a certain prefix).
This commit is contained in:
parent
51d3bb4adf
commit
fdfbaf99d4
|
|
@ -70,7 +70,7 @@ sealed trait RichParser[A]
|
||||||
/** If an exception is thrown by the original Parser,
|
/** If an exception is thrown by the original Parser,
|
||||||
* capture it and fail locally instead of allowing the exception to propagate up and terminate parsing.*/
|
* capture it and fail locally instead of allowing the exception to propagate up and terminate parsing.*/
|
||||||
def failOnException: Parser[A]
|
def failOnException: Parser[A]
|
||||||
|
|
||||||
@deprecated("Use `not` and explicitly provide the failure message", "0.12.2")
|
@deprecated("Use `not` and explicitly provide the failure message", "0.12.2")
|
||||||
def unary_- : Parser[Unit]
|
def unary_- : Parser[Unit]
|
||||||
|
|
||||||
|
|
@ -87,6 +87,9 @@ 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: SourceOfExamples, maxNumberOfExamples: Int): Parser[A]
|
||||||
|
|
||||||
/** 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]
|
||||||
|
|
||||||
|
|
@ -239,7 +242,7 @@ object Parser extends ParserMain
|
||||||
case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
|
case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial match
|
partial match
|
||||||
{
|
{
|
||||||
case Some(part) =>
|
case Some(part) =>
|
||||||
|
|
@ -285,6 +288,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: SourceOfExamples, maxNumberOfExamples: Int): Parser[A] = Parser.examples(a, s, maxNumberOfExamples)
|
||||||
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)
|
||||||
|
|
@ -295,7 +299,7 @@ trait ParserMain
|
||||||
|
|
||||||
/** Construct a parser that is valid, but has no valid result. This is used as a way
|
/** Construct a parser that is valid, but has no valid result. This is used as a way
|
||||||
* to provide a definitive Failure when a parser doesn't match empty input. For example,
|
* to provide a definitive Failure when a parser doesn't match empty input. For example,
|
||||||
* in `softFailure(...) | p`, if `p` doesn't match the empty sequence, the failure will come
|
* in `softFailure(...) | p`, if `p` doesn't match the empty sequence, the failure will come
|
||||||
* from the Parser constructed by the `softFailure` method. */
|
* from the Parser constructed by the `softFailure` method. */
|
||||||
private[sbt] def softFailure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
private[sbt] def softFailure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
||||||
SoftInvalid( mkFailures(msg :: Nil, definitive) )
|
SoftInvalid( mkFailures(msg :: Nil, definitive) )
|
||||||
|
|
@ -430,6 +434,17 @@ trait ParserMain
|
||||||
}
|
}
|
||||||
else a
|
else a
|
||||||
|
|
||||||
|
def examples[A](a: Parser[A], completions: SourceOfExamples, maxNumberOfExamples: Int): Parser[A] =
|
||||||
|
if(a.valid) {
|
||||||
|
a.result match
|
||||||
|
{
|
||||||
|
case Some(av) => success( av )
|
||||||
|
case None =>
|
||||||
|
new DynamicExamples(a, completions, maxNumberOfExamples)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else a
|
||||||
|
|
||||||
def matched(t: Parser[_], seen: Vector[Char] = Vector.empty, partial: Boolean = false): Parser[String] =
|
def matched(t: Parser[_], seen: Vector[Char] = Vector.empty, partial: Boolean = false): Parser[String] =
|
||||||
t match
|
t match
|
||||||
{
|
{
|
||||||
|
|
@ -442,7 +457,7 @@ trait ParserMain
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Establishes delegate parser `t` as a single token of tab completion.
|
/** Establishes delegate parser `t` as a single token of tab completion.
|
||||||
* When tab completion of part of this token is requested, the completions provided by the delegate `t` or a later derivative are appended to
|
* When tab completion of part of this token is requested, the completions provided by the delegate `t` or a later derivative are appended to
|
||||||
* the prefix String already seen by this parser. */
|
* the prefix String already seen by this parser. */
|
||||||
def token[T](t: Parser[T]): Parser[T] = token(t, TokenCompletions.default)
|
def token[T](t: Parser[T]): Parser[T] = token(t, TokenCompletions.default)
|
||||||
|
|
||||||
|
|
@ -703,6 +718,26 @@ 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) + ")"
|
||||||
}
|
}
|
||||||
|
abstract class SourceOfExamples
|
||||||
|
{
|
||||||
|
def apply(): Iterable[String]
|
||||||
|
def withAddedPrefix(addedPrefix: String): SourceOfExamples
|
||||||
|
}
|
||||||
|
private final class DynamicExamples[T](delegate: Parser[T], sourceOfExamples: SourceOfExamples, maxNumberOfExamples: Int = 10) extends ValidParser[T]
|
||||||
|
{
|
||||||
|
def derive(c: Char) = examples(delegate derive c, sourceOfExamples.withAddedPrefix(c.toString), maxNumberOfExamples)
|
||||||
|
def result = delegate.result
|
||||||
|
lazy val resultEmpty = delegate.resultEmpty
|
||||||
|
def completions(level: Int) = {
|
||||||
|
if(sourceOfExamples().isEmpty)
|
||||||
|
if(resultEmpty.isValid) Completions.nil else Completions.empty
|
||||||
|
else {
|
||||||
|
val examplesBasedOnTheResult = sourceOfExamples().take(maxNumberOfExamples).toSet
|
||||||
|
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override def toString = "examples(" + delegate + ", " + sourceOfExamples().take(2).toList + ")"
|
||||||
|
}
|
||||||
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]
|
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]
|
||||||
{
|
{
|
||||||
assert(0 <= start && start < str.length)
|
assert(0 <= start && start < str.length)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package sbt.complete
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.lang.Character.{getType, MATH_SYMBOL, OTHER_SYMBOL, DASH_PUNCTUATION, OTHER_PUNCTUATION, MODIFIER_SYMBOL, CURRENCY_SYMBOL}
|
import java.lang.Character.{getType, MATH_SYMBOL, OTHER_SYMBOL, DASH_PUNCTUATION, OTHER_PUNCTUATION, MODIFIER_SYMBOL, CURRENCY_SYMBOL}
|
||||||
|
import java.nio.file.{Files, Path}
|
||||||
|
|
||||||
/** Provides standard implementations of commonly useful [[Parser]]s. */
|
/** Provides standard implementations of commonly useful [[Parser]]s. */
|
||||||
trait Parsers
|
trait Parsers
|
||||||
|
|
@ -78,7 +79,7 @@ trait Parsers
|
||||||
def isScalaIDChar(c: Char) = c.isLetterOrDigit || c == '_'
|
def isScalaIDChar(c: Char) = c.isLetterOrDigit || c == '_'
|
||||||
|
|
||||||
def isDelimiter(c: Char) = c match { case '`' | '\'' | '\"' | /*';' | */',' | '.' => true ; case _ => false }
|
def isDelimiter(c: Char) = c match { case '`' | '\'' | '\"' | /*';' | */',' | '.' => true ; case _ => false }
|
||||||
|
|
||||||
/** Matches a single character that is not a whitespace character. */
|
/** Matches a single character that is not a whitespace character. */
|
||||||
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
|
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
|
||||||
|
|
||||||
|
|
@ -128,8 +129,32 @@ 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')
|
||||||
|
|
||||||
// TODO: implement
|
class FileExamples(base: Path, prefix: String = "") extends SourceOfExamples {
|
||||||
def fileParser(base: File): Parser[File] = token(mapOrFail(NotSpace)(s => new File(s.mkString)), "<file>")
|
private val prefixPath: String = "." + File.separator + prefix
|
||||||
|
|
||||||
|
override def apply(): Iterable[String] = files(base).map(base.relativize).map(_.toString.substring(prefix.length))
|
||||||
|
|
||||||
|
override def withAddedPrefix(addedPrefix: String): FileExamples = new FileExamples(base, prefix + addedPrefix)
|
||||||
|
|
||||||
|
protected def fileStartsWithPrefix(path: Path): Boolean = path.toString.startsWith(prefixPath)
|
||||||
|
|
||||||
|
protected def directoryStartsWithPrefix(path: Path): Boolean = {
|
||||||
|
val pathString = path.toString
|
||||||
|
pathString.startsWith(prefixPath) || prefixPath.startsWith(pathString)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def files(directory: Path): Iterable[Path] = {
|
||||||
|
import scala.collection.JavaConversions._
|
||||||
|
val subPathStream = Files.newDirectoryStream(directory).toStream
|
||||||
|
val (subDirectories, filesOnly) = subPathStream.partition(path => Files.isDirectory(path))
|
||||||
|
filesOnly.filter(fileStartsWithPrefix) ++ subDirectories.filter(directoryStartsWithPrefix).flatMap(files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def fileParser(base: File, maxNumberOfExamples: Int = 25): Parser[File] =
|
||||||
|
OptSpace ~> StringBasic
|
||||||
|
.examples(new FileExamples(base.toPath), maxNumberOfExamples)
|
||||||
|
.map(new File(_))
|
||||||
|
|
||||||
/** 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>")
|
||||||
|
|
@ -153,7 +178,7 @@ trait Parsers
|
||||||
/** Parses a verbatim quoted String value, discarding the quotes in the result. This kind of quoted text starts with triple quotes `"""`
|
/** Parses a verbatim quoted String value, discarding the quotes in the result. This kind of quoted text starts with triple quotes `"""`
|
||||||
* and ends at the next triple quotes and may contain any character in between. */
|
* and ends at the next triple quotes and may contain any character in between. */
|
||||||
lazy val StringVerbatim: Parser[String] = VerbatimDQuotes ~>
|
lazy val StringVerbatim: Parser[String] = VerbatimDQuotes ~>
|
||||||
any.+.string.filter(!_.contains(VerbatimDQuotes), _ => "Invalid verbatim string") <~
|
any.+.string.filter(!_.contains(VerbatimDQuotes), _ => "Invalid verbatim string") <~
|
||||||
VerbatimDQuotes
|
VerbatimDQuotes
|
||||||
|
|
||||||
/** Parses a string value, interpreting escapes and discarding the surrounding quotes in the result.
|
/** Parses a string value, interpreting escapes and discarding the surrounding quotes in the result.
|
||||||
|
|
@ -168,7 +193,7 @@ trait Parsers
|
||||||
BackslashChar ~> ('b' ^^^ '\b' | 't' ^^^ '\t' | 'n' ^^^ '\n' | 'f' ^^^ '\f' | 'r' ^^^ '\r' |
|
BackslashChar ~> ('b' ^^^ '\b' | 't' ^^^ '\t' | 'n' ^^^ '\n' | 'f' ^^^ '\f' | 'r' ^^^ '\r' |
|
||||||
'\"' ^^^ '\"' | '\'' ^^^ '\'' | '\\' ^^^ '\\' | UnicodeEscape)
|
'\"' ^^^ '\"' | '\'' ^^^ '\'' | '\\' ^^^ '\\' | UnicodeEscape)
|
||||||
|
|
||||||
/** Parses a single unicode escape sequence into the represented Char.
|
/** Parses a single unicode escape sequence into the represented Char.
|
||||||
* A unicode escape begins with a backslash, followed by a `u` and 4 hexadecimal digits representing the unicode value. */
|
* A unicode escape begins with a backslash, followed by a `u` and 4 hexadecimal digits representing the unicode value. */
|
||||||
lazy val UnicodeEscape: Parser[Char] =
|
lazy val UnicodeEscape: Parser[Char] =
|
||||||
("u" ~> repeat(HexDigit, 4, 4)) map { seq => Integer.parseInt(seq.mkString, 16).toChar }
|
("u" ~> repeat(HexDigit, 4, 4)) map { seq => Integer.parseInt(seq.mkString, 16).toChar }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue