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:
Matej Urbas 2014-04-05 19:46:27 +01:00
parent 51d3bb4adf
commit fdfbaf99d4
2 changed files with 69 additions and 9 deletions

View File

@ -87,6 +87,9 @@ sealed trait RichParser[A]
/** Explicitly defines the completions for the original Parser.*/
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.*/
def string(implicit ev: A <:< Seq[Char]): Parser[String]
@ -285,6 +288,7 @@ trait ParserMain
def - (o: Parser[_]) = sub(a, o)
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: 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 string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
@ -430,6 +434,17 @@ trait ParserMain
}
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] =
t match
{
@ -703,6 +718,26 @@ private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends
Completions(fixed map(f => Completion.suggestion(f)) )
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]
{
assert(0 <= start && start < str.length)

View File

@ -7,6 +7,7 @@ package sbt.complete
import java.io.File
import java.net.URI
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. */
trait Parsers
@ -128,8 +129,32 @@ trait Parsers
/** 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')
// TODO: implement
def fileParser(base: File): Parser[File] = token(mapOrFail(NotSpace)(s => new File(s.mkString)), "<file>")
class FileExamples(base: Path, prefix: String = "") extends SourceOfExamples {
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>`. */
lazy val Port = token(IntBasic, "<port>")