mirror of https://github.com/sbt/sbt.git
Port util-logging
This commit is contained in:
parent
c724e83fd1
commit
50b062b795
|
|
@ -181,7 +181,11 @@ val scriptedSbtReduxMimaSettings = Def.settings(mimaPreviousArtifacts := Set())
|
|||
|
||||
lazy val sbtRoot: Project = (project in file("."))
|
||||
// .aggregate(nonRoots: _*)
|
||||
.aggregate(collectionProj, coreMacrosProj)
|
||||
.aggregate(
|
||||
collectionProj,
|
||||
coreMacrosProj,
|
||||
utilLogging,
|
||||
)
|
||||
.settings(
|
||||
minimalSettings,
|
||||
onLoadMessage := {
|
||||
|
|
@ -351,9 +355,9 @@ lazy val utilLogging = (project in file("internal") / "util-logging")
|
|||
log4jCore,
|
||||
disruptor,
|
||||
sjsonNewScalaJson.value,
|
||||
scalaReflect.value
|
||||
),
|
||||
libraryDependencies ++= Seq(scalacheck % "test", scalatest % "test"),
|
||||
Compile / generateContrabands / contrabandCodecsDependencies := List(sjsonNewCore.value),
|
||||
Compile / scalacOptions ++= (scalaVersion.value match {
|
||||
case v if v.startsWith("2.12.") => List("-Ywarn-unused:-locals,-explicits,-privates")
|
||||
case _ => List()
|
||||
|
|
|
|||
|
|
@ -125,9 +125,11 @@ object LineReader {
|
|||
case _: Terminal.ConsoleTerminal => Some(Signals.register(() => terminal.write(-1)))
|
||||
case _ => None
|
||||
}
|
||||
try terminal.withRawInput {
|
||||
Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt)))
|
||||
} catch {
|
||||
try
|
||||
terminal.withRawInput {
|
||||
Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt)))
|
||||
}
|
||||
catch {
|
||||
case e: EndOfFileException =>
|
||||
if (terminal == Terminal.console && System.console == null) None
|
||||
else Some("exit")
|
||||
|
|
@ -195,8 +197,8 @@ abstract class JLine extends LineReader {
|
|||
|
||||
private[this] def readLineDirect(prompt: String, mask: Option[Char]): Option[String] =
|
||||
if (handleCONT)
|
||||
Signals.withHandler(() => resume(), signal = Signals.CONT)(
|
||||
() => readLineDirectRaw(prompt, mask)
|
||||
Signals.withHandler(() => resume(), signal = Signals.CONT)(() =>
|
||||
readLineDirectRaw(prompt, mask)
|
||||
)
|
||||
else
|
||||
readLineDirectRaw(prompt, mask)
|
||||
|
|
@ -253,8 +255,8 @@ private[sbt] object JLine {
|
|||
private[sbt] def terminal: jline.Terminal = Terminal.deprecatedTeminal
|
||||
|
||||
/**
|
||||
* For accessing the JLine Terminal object.
|
||||
* This ensures synchronized access as well as re-enabling echo after getting the Terminal.
|
||||
* For accessing the JLine Terminal object. This ensures synchronized access as well as
|
||||
* re-enabling echo after getting the Terminal.
|
||||
*/
|
||||
@deprecated(
|
||||
"Don't use jline.Terminal directly. Use Terminal.get.withCanonicalIn instead.",
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ package sbt.internal.util
|
|||
package complete
|
||||
|
||||
/**
|
||||
* Represents a set of completions.
|
||||
* It exists instead of implicitly defined operations on top of Set[Completion]
|
||||
* for laziness.
|
||||
* Represents a set of completions. It exists instead of implicitly defined operations on top of
|
||||
* Set[Completion] for laziness.
|
||||
*/
|
||||
sealed trait Completions {
|
||||
def get: Set[Completion]
|
||||
|
|
@ -46,49 +45,48 @@ object Completions {
|
|||
def strict(cs: Set[Completion]): Completions = apply(cs)
|
||||
|
||||
/**
|
||||
* No suggested completions, not even the empty Completion.
|
||||
* This typically represents invalid input.
|
||||
* No suggested completions, not even the empty Completion. This typically represents invalid
|
||||
* input.
|
||||
*/
|
||||
val nil: Completions = strict(Set.empty)
|
||||
|
||||
/**
|
||||
* Only includes an empty Suggestion.
|
||||
* This typically represents valid input that either has no completions or accepts no further input.
|
||||
* Only includes an empty Suggestion. This typically represents valid input that either has no
|
||||
* completions or accepts no further input.
|
||||
*/
|
||||
val empty: Completions = strict(Set.empty + Completion.empty)
|
||||
|
||||
/** Returns a strict Completions instance containing only the provided Completion.*/
|
||||
/** Returns a strict Completions instance containing only the provided Completion. */
|
||||
def single(c: Completion): Completions = strict(Set.empty + c)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a completion.
|
||||
* The abstract members `display` and `append` are best explained with an example.
|
||||
* Represents a completion. The abstract members `display` and `append` are best explained with an
|
||||
* example.
|
||||
*
|
||||
* Assuming space-delimited tokens, processing this:
|
||||
* am is are w<TAB>
|
||||
* could produce these Completions:
|
||||
* Completion { display = "was"; append = "as" }
|
||||
* Completion { display = "were"; append = "ere" }
|
||||
* to suggest the tokens "was" and "were".
|
||||
* Assuming space-delimited tokens, processing this: am is are w<TAB> could produce these
|
||||
* Completions: Completion { display = "was"; append = "as" } Completion { display = "were"; append
|
||||
* = "ere" } to suggest the tokens "was" and "were".
|
||||
*
|
||||
* In this way, two pieces of information are preserved:
|
||||
* 1) what needs to be appended to the current input if a completion is selected
|
||||
* 2) the full token being completed, which is useful for presenting a user with choices to select
|
||||
* In this way, two pieces of information are preserved: 1) what needs to be appended to the current
|
||||
* input if a completion is selected 2) the full token being completed, which is useful for
|
||||
* presenting a user with choices to select
|
||||
*/
|
||||
sealed trait Completion {
|
||||
|
||||
/** The proposed suffix to append to the existing input to complete the last token in the input.*/
|
||||
/**
|
||||
* The proposed suffix to append to the existing input to complete the last token in the input.
|
||||
*/
|
||||
def append: String
|
||||
|
||||
/** The string to present to the user to represent the full token being suggested.*/
|
||||
/** The string to present to the user to represent the full token being suggested. */
|
||||
def display: String
|
||||
|
||||
/** True if this Completion is suggesting the empty string.*/
|
||||
/** True if this Completion is suggesting the empty string. */
|
||||
def isEmpty: Boolean
|
||||
|
||||
/** Appends the completions in `o` with the completions in this Completion.*/
|
||||
/** Appends the completions in `o` with the completions in this Completion. */
|
||||
def ++(o: Completion): Completion = Completion.concat(this, o)
|
||||
|
||||
final def x(o: Completions): Completions =
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ import java.lang.Character.{ toLowerCase => lower }
|
|||
object EditDistance {
|
||||
|
||||
/**
|
||||
* Translated from the java version at
|
||||
* http://www.merriampark.com/ld.htm
|
||||
* which is declared to be public domain.
|
||||
* Translated from the java version at http://www.merriampark.com/ld.htm which is declared to be
|
||||
* public domain.
|
||||
*/
|
||||
def levenshtein(
|
||||
s: String,
|
||||
|
|
|
|||
|
|
@ -12,22 +12,25 @@ import java.io.File
|
|||
import sbt.io.IO
|
||||
|
||||
/**
|
||||
* These sources of examples are used in parsers for user input completion. An example of such a source is the
|
||||
* [[sbt.internal.util.complete.FileExamples]] class, which provides a list of suggested files to the user as they press the
|
||||
* TAB key in the console.
|
||||
* These sources of examples are used in parsers for user input completion. An example of such a
|
||||
* source is the [[sbt.internal.util.complete.FileExamples]] class, which provides a list of
|
||||
* suggested files to the user as they press the TAB key in the console.
|
||||
*/
|
||||
trait ExampleSource {
|
||||
|
||||
/**
|
||||
* @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]].
|
||||
* @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]].
|
||||
*/
|
||||
def apply(): Iterable[String]
|
||||
|
||||
/**
|
||||
* @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
|
||||
* the just added prefix).
|
||||
* @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 the just added prefix).
|
||||
*/
|
||||
def withAddedPrefix(addedPrefix: String): ExampleSource
|
||||
|
||||
|
|
@ -35,7 +38,8 @@ trait ExampleSource {
|
|||
|
||||
/**
|
||||
* A convenience example source that wraps any collection of strings into a source of examples.
|
||||
* @param examples the examples that will be displayed to the user when they press the TAB key.
|
||||
* @param examples
|
||||
* the 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 =
|
||||
|
|
@ -50,8 +54,10 @@ sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSo
|
|||
|
||||
/**
|
||||
* Provides path completion examples based on files in the base directory.
|
||||
* @param base the directory within which this class will search for completion examples.
|
||||
* @param prefix the part of the path already written by the user.
|
||||
* @param base
|
||||
* the directory within which this class will search for completion examples.
|
||||
* @param prefix
|
||||
* the part of the path already written by the user.
|
||||
*/
|
||||
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
|
||||
override def apply(): Stream[String] = files(base).map(_ substring prefix.length)
|
||||
|
|
@ -64,7 +70,9 @@ class FileExamples(base: File, prefix: String = "") extends ExampleSource {
|
|||
val prefixedDirectChildPaths = childPaths map { IO.relativize(base, _).get } filter {
|
||||
_ startsWith prefix
|
||||
}
|
||||
val dirsToRecurseInto = childPaths filter { _.isDirectory } map { IO.relativize(base, _).get } filter {
|
||||
val dirsToRecurseInto = childPaths filter { _.isDirectory } map {
|
||||
IO.relativize(base, _).get
|
||||
} filter {
|
||||
dirStartsWithPrefix
|
||||
}
|
||||
prefixedDirectChildPaths append dirsToRecurseInto.flatMap(dir => files(new File(base, dir)))
|
||||
|
|
|
|||
|
|
@ -61,14 +61,12 @@ object HistoryCommands {
|
|||
{ printHistory(h, MaxLines, show); nil[String].some }
|
||||
}
|
||||
|
||||
lazy val execStr = flag('?') ~ token(any.+.string, "<string>") map {
|
||||
case (contains, str) =>
|
||||
execute(h => if (contains) h !? str else h ! str)
|
||||
lazy val execStr = flag('?') ~ token(any.+.string, "<string>") map { case (contains, str) =>
|
||||
execute(h => if (contains) h !? str else h ! str)
|
||||
}
|
||||
|
||||
lazy val execInt = flag('-') ~ num map {
|
||||
case (neg, value) =>
|
||||
execute(h => if (neg) h !- value else h ! value)
|
||||
lazy val execInt = flag('-') ~ num map { case (neg, value) =>
|
||||
execute(h => if (neg) h !- value else h ! value)
|
||||
}
|
||||
|
||||
lazy val help = success((h: History) => { printHelp(); nil[String].some })
|
||||
|
|
|
|||
|
|
@ -81,10 +81,9 @@ object JLineCompletion {
|
|||
|
||||
def convertCompletions(cs: Set[Completion]): (Seq[String], Seq[String]) = {
|
||||
val (insert, display) =
|
||||
cs.foldLeft((Set.empty[String], Set.empty[String])) {
|
||||
case (t @ (insert, display), comp) =>
|
||||
if (comp.isEmpty) t
|
||||
else (appendNonEmpty(insert, comp.append), appendNonEmpty(display, comp.display))
|
||||
cs.foldLeft((Set.empty[String], Set.empty[String])) { case (t @ (insert, display), comp) =>
|
||||
if (comp.isEmpty) t
|
||||
else (appendNonEmpty(insert, comp.append), appendNonEmpty(display, comp.display))
|
||||
}
|
||||
(insert.toSeq, display.toSeq.sorted)
|
||||
}
|
||||
|
|
@ -135,8 +134,8 @@ object JLineCompletion {
|
|||
}
|
||||
|
||||
/**
|
||||
* `display` is assumed to be the exact strings requested to be displayed.
|
||||
* In particular, duplicates should have been removed already.
|
||||
* `display` is assumed to be the exact strings requested to be displayed. In particular,
|
||||
* duplicates should have been removed already.
|
||||
*/
|
||||
def showCompletions(display: Seq[String], reader: ConsoleReader): Unit = {
|
||||
printCompletions(display, reader)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import sbt.internal.util.Types.{ left, right, some }
|
|||
import sbt.internal.util.Util.{ makeList, separate }
|
||||
|
||||
/**
|
||||
* A String parser that provides semi-automatic tab completion.
|
||||
* A successful parse results in a value of type `T`.
|
||||
* The methods in this trait are what must be implemented to define a new Parser implementation, but are not typically useful for common usage.
|
||||
* Instead, most useful methods for combining smaller parsers into larger parsers are implicitly added by the [[RichParser]] type.
|
||||
* A String parser that provides semi-automatic tab completion. A successful parse results in a
|
||||
* value of type `T`. The methods in this trait are what must be implemented to define a new Parser
|
||||
* implementation, but are not typically useful for common usage. Instead, most useful methods for
|
||||
* combining smaller parsers into larger parsers are implicitly added by the [[RichParser]] type.
|
||||
*/
|
||||
trait Parser[+T] {
|
||||
def derive(i: Char): Parser[T]
|
||||
|
|
@ -31,80 +31,93 @@ trait Parser[+T] {
|
|||
|
||||
sealed trait RichParser[A] {
|
||||
|
||||
/** Apply the original Parser and then apply `next` (in order). The result of both is provides as a pair. */
|
||||
/**
|
||||
* Apply the original Parser and then apply `next` (in order). The result of both is provides as a
|
||||
* pair.
|
||||
*/
|
||||
def ~[B](next: Parser[B]): Parser[(A, B)]
|
||||
|
||||
/** Apply the original Parser one or more times and provide the non-empty sequence of results.*/
|
||||
/** Apply the original Parser one or more times and provide the non-empty sequence of results. */
|
||||
def + : Parser[Seq[A]]
|
||||
|
||||
/** Apply the original Parser zero or more times and provide the (potentially empty) sequence of results.*/
|
||||
/**
|
||||
* Apply the original Parser zero or more times and provide the (potentially empty) sequence of
|
||||
* results.
|
||||
*/
|
||||
def * : Parser[Seq[A]]
|
||||
|
||||
/** Apply the original Parser zero or one times, returning None if it was applied zero times or the result wrapped in Some if it was applied once.*/
|
||||
/**
|
||||
* Apply the original Parser zero or one times, returning None if it was applied zero times or the
|
||||
* result wrapped in Some if it was applied once.
|
||||
*/
|
||||
def ? : Parser[Option[A]]
|
||||
|
||||
/** Apply either the original Parser or `b`.*/
|
||||
/** Apply either the original Parser or `b`. */
|
||||
def |[B >: A](b: Parser[B]): Parser[B]
|
||||
|
||||
/** Apply either the original Parser or `b`.*/
|
||||
/** Apply either the original Parser or `b`. */
|
||||
def ||[B](b: Parser[B]): Parser[Either[A, B]]
|
||||
|
||||
/** Apply the original Parser to the input and then apply `f` to the result.*/
|
||||
/** Apply the original Parser to the input and then apply `f` to the result. */
|
||||
def map[B](f: A => B): Parser[B]
|
||||
|
||||
/**
|
||||
* Returns the original parser. This is useful for converting literals to Parsers.
|
||||
* For example, `'c'.id` or `"asdf".id`
|
||||
* Returns the original parser. This is useful for converting literals to Parsers. For example,
|
||||
* `'c'.id` or `"asdf".id`
|
||||
*/
|
||||
def id: Parser[A]
|
||||
|
||||
/** Apply the original Parser, but provide `value` as the result if it succeeds. */
|
||||
def ^^^[B](value: B): Parser[B]
|
||||
|
||||
/** Apply the original Parser, but provide `alt` as the result if it fails.*/
|
||||
/** Apply the original Parser, but provide `alt` as the result if it fails. */
|
||||
def ??[B >: A](alt: B): Parser[B]
|
||||
|
||||
/**
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of `next`.
|
||||
* (The arrow point in the direction of the retained result.)
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order),
|
||||
* discarding the result of `next`. (The arrow point in the direction of the retained result.)
|
||||
*/
|
||||
def <~[B](b: Parser[B]): Parser[A]
|
||||
|
||||
/**
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order), discarding the result of the original parser.
|
||||
* (The arrow point in the direction of the retained result.)
|
||||
* Produces a Parser that applies the original Parser and then applies `next` (in order),
|
||||
* discarding the result of the original parser. (The arrow point in the direction of the retained
|
||||
* result.)
|
||||
*/
|
||||
def ~>[B](b: Parser[B]): Parser[B]
|
||||
|
||||
/** Uses the specified message if the original Parser fails.*/
|
||||
/** Uses the specified message if the original Parser fails. */
|
||||
def !!!(msg: String): Parser[A]
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
def failOnException: Parser[A]
|
||||
|
||||
/**
|
||||
* Apply the original parser, but only succeed if `o` also succeeds.
|
||||
* Note that `o` does not need to consume the same amount of input to satisfy this condition.
|
||||
* Apply the original parser, but only succeed if `o` also succeeds. Note that `o` does not need
|
||||
* to consume the same amount of input to satisfy this condition.
|
||||
*/
|
||||
def &(o: Parser[_]): Parser[A]
|
||||
|
||||
/** Explicitly defines the completions for the original Parser.*/
|
||||
/** Explicitly defines the completions for the original Parser. */
|
||||
def examples(s: String*): Parser[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]
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @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,
|
||||
|
|
@ -113,24 +126,30 @@ sealed trait RichParser[A] {
|
|||
): 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.
|
||||
* @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]
|
||||
|
||||
/**
|
||||
* Produces a Parser that filters the original parser.
|
||||
* If 'f' is not true when applied to the output of the original parser, the Parser returned by this method fails.
|
||||
* The failure message is constructed by applying `msg` to the String that was successfully parsed by the original parser.
|
||||
* Produces a Parser that filters the original parser. If 'f' is not true when applied to the
|
||||
* output of the original parser, the Parser returned by this method fails. The failure message is
|
||||
* constructed by applying `msg` to the String that was successfully parsed by the original
|
||||
* parser.
|
||||
*/
|
||||
def filter(f: A => Boolean, msg: String => String): Parser[A]
|
||||
|
||||
/** Applies the original parser, applies `f` to the result to get the next parser, and applies that parser and uses its result for the overall result. */
|
||||
/**
|
||||
* Applies the original parser, applies `f` to the result to get the next parser, and applies that
|
||||
* parser and uses its result for the overall result.
|
||||
*/
|
||||
def flatMap[B](f: A => Parser[B]): Parser[B]
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +334,7 @@ object Parser extends ParserMain {
|
|||
|
||||
trait ParserMain {
|
||||
|
||||
/** Provides combinators for Parsers.*/
|
||||
/** Provides combinators for Parsers. */
|
||||
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A] {
|
||||
def ~[B](b: Parser[B]) = seqParser(a, b)
|
||||
def ||[B](b: Parser[B]) = choiceParser(a, b)
|
||||
|
|
@ -357,29 +376,29 @@ trait ParserMain {
|
|||
implicit def literalRichStringParser(s: String): RichParser[String] = richParser(s)
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* in `softFailure(...) | p`, if `p` doesn't match the empty sequence, the failure will come
|
||||
* from the Parser constructed by the `softFailure` method.
|
||||
* 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, in `softFailure(...) |
|
||||
* p`, if `p` doesn't match the empty sequence, the failure will come from the Parser constructed
|
||||
* by the `softFailure` method.
|
||||
*/
|
||||
private[sbt] def softFailure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
||||
SoftInvalid(mkFailures(msg :: Nil, definitive))
|
||||
|
||||
/**
|
||||
* Defines a parser that always fails on any input with messages `msgs`.
|
||||
* If `definitive` is `true`, any failures by later alternatives are discarded.
|
||||
* Defines a parser that always fails on any input with messages `msgs`. If `definitive` is
|
||||
* `true`, any failures by later alternatives are discarded.
|
||||
*/
|
||||
def invalid(msgs: => Seq[String], definitive: Boolean = false): Parser[Nothing] =
|
||||
Invalid(mkFailures(msgs, definitive))
|
||||
|
||||
/**
|
||||
* Defines a parser that always fails on any input with message `msg`.
|
||||
* If `definitive` is `true`, any failures by later alternatives are discarded.
|
||||
* Defines a parser that always fails on any input with message `msg`. If `definitive` is `true`,
|
||||
* any failures by later alternatives are discarded.
|
||||
*/
|
||||
def failure(msg: => String, definitive: Boolean = false): Parser[Nothing] =
|
||||
invalid(msg :: Nil, definitive)
|
||||
|
||||
/** Defines a parser that always succeeds on empty input with the result `value`.*/
|
||||
/** Defines a parser that always succeeds on empty input with the result `value`. */
|
||||
def success[T](value: T): Parser[T] = new ValidParser[T] {
|
||||
override def result = Some(value)
|
||||
def resultEmpty = Value(value)
|
||||
|
|
@ -388,25 +407,29 @@ trait ParserMain {
|
|||
override def toString = "success(" + value + ")"
|
||||
}
|
||||
|
||||
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
|
||||
/**
|
||||
* Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.
|
||||
*/
|
||||
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = {
|
||||
val label = r.map(_.toString).toString
|
||||
range(r, label)
|
||||
}
|
||||
|
||||
/** Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.*/
|
||||
/**
|
||||
* Presents a Char range as a Parser. A single Char is parsed only if it is in the given range.
|
||||
*/
|
||||
def range(r: collection.immutable.NumericRange[Char], label: String): Parser[Char] =
|
||||
charClass(r contains _, label).examples(r.map(_.toString): _*)
|
||||
|
||||
/** Defines a Parser that parses a single character only if it is contained in `legal`.*/
|
||||
/** Defines a Parser that parses a single character only if it is contained in `legal`. */
|
||||
def chars(legal: String): Parser[Char] = {
|
||||
val set = legal.toSet
|
||||
charClass(set, "character in '" + legal + "'") examples (set.map(_.toString))
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a Parser that parses a single character only if the predicate `f` returns true for that character.
|
||||
* If this parser fails, `label` is used as the failure message.
|
||||
* Defines a Parser that parses a single character only if the predicate `f` returns true for that
|
||||
* character. If this parser fails, `label` is used as the failure message.
|
||||
*/
|
||||
def charClass(f: Char => Boolean, label: String = "<unspecified>"): Parser[Char] =
|
||||
new CharacterClass(f, label)
|
||||
|
|
@ -420,18 +443,24 @@ trait ParserMain {
|
|||
override def toString = "'" + ch + "'"
|
||||
}
|
||||
|
||||
/** Presents a literal String `s` as a Parser that only parses that exact text and provides it as the result.*/
|
||||
/**
|
||||
* Presents a literal String `s` as a Parser that only parses that exact text and provides it as
|
||||
* the result.
|
||||
*/
|
||||
implicit def literal(s: String): Parser[String] = stringLiteral(s, 0)
|
||||
|
||||
/** See [[unapply]]. */
|
||||
object ~ {
|
||||
|
||||
/** Convenience for destructuring a tuple that mirrors the `~` combinator.*/
|
||||
/** Convenience for destructuring a tuple that mirrors the `~` combinator. */
|
||||
def unapply[A, B](t: (A, B)): Some[(A, B)] = Some(t)
|
||||
|
||||
}
|
||||
|
||||
/** Parses input `str` using `parser`. If successful, the result is provided wrapped in `Right`. If unsuccessful, an error message is provided in `Left`.*/
|
||||
/**
|
||||
* Parses input `str` using `parser`. If successful, the result is provided wrapped in `Right`. If
|
||||
* unsuccessful, an error message is provided in `Left`.
|
||||
*/
|
||||
def parse[T](str: String, parser: Parser[T]): Either[String, T] =
|
||||
Parser.result(parser, str).left.map { failures =>
|
||||
val (msgs, pos) = failures()
|
||||
|
|
@ -439,11 +468,10 @@ trait ParserMain {
|
|||
}
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str`.
|
||||
* If `completions` is true, the available completions for the input are displayed.
|
||||
* Otherwise, the result of parsing is printed using the result's `toString` method.
|
||||
* If parsing fails, the error message is displayed.
|
||||
* Convenience method to use when developing a parser. `parser` is applied to the input `str`. If
|
||||
* `completions` is true, the available completions for the input are displayed. Otherwise, the
|
||||
* result of parsing is printed using the result's `toString` method. If parsing fails, the error
|
||||
* message is displayed.
|
||||
*
|
||||
* See also [[sampleParse]] and [[sampleCompletions]].
|
||||
*/
|
||||
|
|
@ -451,9 +479,9 @@ trait ParserMain {
|
|||
if (completions) sampleCompletions(str, parser) else sampleParse(str, parser)
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str` and the result of parsing is printed using the result's `toString` method.
|
||||
* If parsing fails, the error message is displayed.
|
||||
* Convenience method to use when developing a parser. `parser` is applied to the input `str` and
|
||||
* the result of parsing is printed using the result's `toString` method. If parsing fails, the
|
||||
* error message is displayed.
|
||||
*/
|
||||
def sampleParse(str: String, parser: Parser[_]): Unit =
|
||||
parse(str, parser) match {
|
||||
|
|
@ -462,9 +490,9 @@ trait ParserMain {
|
|||
}
|
||||
|
||||
/**
|
||||
* Convenience method to use when developing a parser.
|
||||
* `parser` is applied to the input `str` and the available completions are displayed on separate lines.
|
||||
* If parsing fails, the error message is displayed.
|
||||
* Convenience method to use when developing a parser. `parser` is applied to the input `str` and
|
||||
* the available completions are displayed on separate lines. If parsing fails, the error message
|
||||
* is displayed.
|
||||
*/
|
||||
def sampleCompletions(str: String, parser: Parser[_], level: Int = 1): Unit =
|
||||
Parser.completions(parser, str, level).get foreach println
|
||||
|
|
@ -481,7 +509,8 @@ trait ParserMain {
|
|||
val msgs = msgs0()
|
||||
val nonEmpty = if (msgs.isEmpty) Seq("Unexpected end of input") else msgs
|
||||
(nonEmpty, ci)
|
||||
} else
|
||||
}
|
||||
else
|
||||
loop(ci, a derive s(ci))
|
||||
}
|
||||
loop(-1, p)
|
||||
|
|
@ -496,10 +525,10 @@ trait ParserMain {
|
|||
if (p.valid) p.derive(c) else p
|
||||
|
||||
/**
|
||||
* Applies parser `p` to input `s` and returns the completions at verbosity `level`.
|
||||
* The interpretation of `level` is up to parser definitions, but 0 is the default by convention,
|
||||
* with increasing positive numbers corresponding to increasing verbosity. Typically no more than
|
||||
* a few levels are defined.
|
||||
* Applies parser `p` to input `s` and returns the completions at verbosity `level`. The
|
||||
* interpretation of `level` is up to parser definitions, but 0 is the default by convention, with
|
||||
* increasing positive numbers corresponding to increasing verbosity. Typically no more than a few
|
||||
* levels are defined.
|
||||
*/
|
||||
def completions(p: Parser[_], s: String, level: Int): Completions =
|
||||
// The x Completions.empty removes any trailing token completions where append.isEmpty
|
||||
|
|
@ -509,14 +538,20 @@ trait ParserMain {
|
|||
examples(a, new FixedSetExamples(completions), completions.size, check)
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @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](
|
||||
|
|
@ -548,31 +583,33 @@ trait ParserMain {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* the prefix String already seen by this parser.
|
||||
* 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 the prefix String already seen by this parser.
|
||||
*/
|
||||
def token[T](t: Parser[T]): Parser[T] = token(t, TokenCompletions.default)
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, no completions are returned if `hide` returns true for the current tab completion level.
|
||||
* Otherwise, the completions provided by the delegate `t` or a later derivative are appended to the prefix String already seen by this parser.
|
||||
* Establishes delegate parser `t` as a single token of tab completion. When tab completion of
|
||||
* part of this token is requested, no completions are returned if `hide` returns true for the
|
||||
* current tab completion level. Otherwise, the completions provided by the delegate `t` or a
|
||||
* later derivative are appended to the prefix String already seen by this parser.
|
||||
*/
|
||||
def token[T](t: Parser[T], hide: Int => Boolean): Parser[T] =
|
||||
token(t, TokenCompletions.default.hideWhen(hide))
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, `description` is displayed for suggestions and no completions are ever performed.
|
||||
* Establishes delegate parser `t` as a single token of tab completion. When tab completion of
|
||||
* part of this token is requested, `description` is displayed for suggestions and no completions
|
||||
* are ever performed.
|
||||
*/
|
||||
def token[T](t: Parser[T], description: String): Parser[T] =
|
||||
token(t, TokenCompletions.displayOnly(description))
|
||||
|
||||
/**
|
||||
* Establishes delegate parser `t` as a single token of tab completion.
|
||||
* When tab completion of part of this token is requested, `display` is used as the printed suggestion, but the completions from the delegate
|
||||
* parser `t` are used to complete if unambiguous.
|
||||
* Establishes delegate parser `t` as a single token of tab completion. When tab completion of
|
||||
* part of this token is requested, `display` is used as the printed suggestion, but the
|
||||
* completions from the delegate parser `t` are used to complete if unambiguous.
|
||||
*/
|
||||
def tokenDisplay[T](t: Parser[T], display: String): Parser[T] =
|
||||
token(t, TokenCompletions.overrideDisplay(display))
|
||||
|
|
@ -842,19 +879,25 @@ private final class Not(delegate: Parser[_], failMessage: String) extends ValidP
|
|||
}
|
||||
|
||||
/**
|
||||
* This class wraps an existing parser (the delegate), and replaces the delegate's completions with examples from
|
||||
* the given example source.
|
||||
* This class wraps an existing parser (the delegate), and replaces the delegate's completions with
|
||||
* examples from the given example source.
|
||||
*
|
||||
* This class asks the example source for a limited amount of examples (to prevent lengthy and expensive
|
||||
* computations and large amounts of allocated data). It then passes these examples on to the UI.
|
||||
* This class asks the example source for a limited amount of examples (to prevent lengthy and
|
||||
* expensive computations and large amounts of allocated data). It then passes these examples on to
|
||||
* the UI.
|
||||
*
|
||||
* @param delegate the parser to decorate with completion examples (i.e., completion of user input).
|
||||
* @param exampleSource the source from which this class will take examples (potentially filter them with the delegate
|
||||
* parser), and pass them to the UI.
|
||||
* @param maxNumberOfExamples the maximum number of completions to read from the example source and pass to the UI. This
|
||||
* limit prevents lengthy example generation and allocation of large amounts of memory.
|
||||
* @param removeInvalidExamples indicates whether to remove examples that are deemed invalid by the delegate parser.
|
||||
* @tparam T the type of value produced by the parser.
|
||||
* @param delegate
|
||||
* the parser to decorate with completion examples (i.e., completion of user input).
|
||||
* @param exampleSource
|
||||
* the source from which this class will take examples (potentially filter them with the delegate
|
||||
* parser), and pass them to the UI.
|
||||
* @param maxNumberOfExamples
|
||||
* the maximum number of completions to read from the example source and pass to the UI. This
|
||||
* limit prevents lengthy example generation and allocation of large amounts of memory.
|
||||
* @param removeInvalidExamples
|
||||
* indicates whether to remove examples that are deemed invalid by the delegate parser.
|
||||
* @tparam T
|
||||
* the type of value produced by the parser.
|
||||
*/
|
||||
private final class ParserWithExamples[T](
|
||||
delegate: Parser[T],
|
||||
|
|
@ -876,8 +919,7 @@ private final class ParserWithExamples[T](
|
|||
lazy val resultEmpty = delegate.resultEmpty
|
||||
|
||||
def completions(level: Int) = {
|
||||
if (exampleSource().isEmpty)
|
||||
if (resultEmpty.isValid) Completions.nil else Completions.empty
|
||||
if (exampleSource().isEmpty) if (resultEmpty.isValid) Completions.nil else Completions.empty
|
||||
else {
|
||||
val examplesBasedOnTheResult = filteredExamples.take(maxNumberOfExamples).toSet
|
||||
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ trait Parsers {
|
|||
/** Parses any single character and provides that character as the result. */
|
||||
lazy val any: Parser[Char] = charClass(_ => true, "any character")
|
||||
|
||||
/** Set that contains each digit in a String representation.*/
|
||||
/** Set that contains each digit in a String representation. */
|
||||
lazy val DigitSet = Set("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
|
||||
|
||||
/** Parses any single digit and provides that digit as a Char as the result.*/
|
||||
/** Parses any single digit and provides that digit as a Char as the result. */
|
||||
lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet
|
||||
|
||||
/** Set containing Chars for hexadecimal digits 0-9 and A-F (but not a-f). */
|
||||
|
|
@ -57,34 +57,57 @@ trait Parsers {
|
|||
/** Parses a single letter, according to Char.isLower, into a Char. */
|
||||
lazy val Lower = charClass(_.isLower, "lower")
|
||||
|
||||
/** Parses the first Char in an sbt identifier, which must be a [[Letter]].*/
|
||||
/** Parses the first Char in an sbt identifier, which must be a [[Letter]]. */
|
||||
def IDStart = Letter
|
||||
|
||||
/** Parses an identifier Char other than the first character. This includes letters, digits, dash `-`, and underscore `_`.*/
|
||||
/**
|
||||
* Parses an identifier Char other than the first character. This includes letters, digits, dash
|
||||
* `-`, and underscore `_`.
|
||||
*/
|
||||
lazy val IDChar = charClass(isIDChar, "ID character")
|
||||
|
||||
/** Parses an identifier String, which must start with [[IDStart]] and contain zero or more [[IDChar]]s after that. */
|
||||
/**
|
||||
* Parses an identifier String, which must start with [[IDStart]] and contain zero or more
|
||||
* [[IDChar]]s after that.
|
||||
*/
|
||||
lazy val ID = identifier(IDStart, IDChar)
|
||||
|
||||
/** Parses a single operator Char, as allowed by [[isOpChar]]. */
|
||||
lazy val OpChar = charClass(isOpChar, "symbol")
|
||||
|
||||
/** Parses a non-empty operator String, which consists only of characters allowed by [[OpChar]]. */
|
||||
/**
|
||||
* Parses a non-empty operator String, which consists only of characters allowed by [[OpChar]].
|
||||
*/
|
||||
lazy val Op = OpChar.+.string
|
||||
|
||||
/** Parses either an operator String defined by [[Op]] or a non-symbolic identifier defined by [[ID]]. */
|
||||
/**
|
||||
* Parses either an operator String defined by [[Op]] or a non-symbolic identifier defined by
|
||||
* [[ID]].
|
||||
*/
|
||||
lazy val OpOrID = ID | Op
|
||||
|
||||
/** Parses a single, non-symbolic Scala identifier Char. Valid characters are letters, digits, and the underscore character `_`. */
|
||||
/**
|
||||
* Parses a single, non-symbolic Scala identifier Char. Valid characters are letters, digits, and
|
||||
* the underscore character `_`.
|
||||
*/
|
||||
lazy val ScalaIDChar = charClass(isScalaIDChar, "Scala identifier character")
|
||||
|
||||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[IDStart]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
/**
|
||||
* Parses a non-symbolic Scala-like identifier. The identifier must start with [[IDStart]] and
|
||||
* contain zero or more [[ScalaIDChar]]s after that.
|
||||
*/
|
||||
lazy val ScalaID = identifier(IDStart, ScalaIDChar)
|
||||
|
||||
/** Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and contain zero or more [[ScalaIDChar]]s after that.*/
|
||||
/**
|
||||
* Parses a non-symbolic Scala-like identifier. The identifier must start with [[Upper]] and
|
||||
* contain zero or more [[ScalaIDChar]]s after that.
|
||||
*/
|
||||
lazy val CapitalizedID = identifier(Upper, ScalaIDChar)
|
||||
|
||||
/** Parses a String that starts with `start` and is followed by zero or more characters parsed by `rep`.*/
|
||||
/**
|
||||
* Parses a String that starts with `start` and is followed by zero or more characters parsed by
|
||||
* `rep`.
|
||||
*/
|
||||
def identifier(start: Parser[Char], rep: Parser[Char]): Parser[String] =
|
||||
start ~ rep.* map { case x ~ xs => (x +: xs).mkString }
|
||||
|
||||
|
|
@ -102,7 +125,7 @@ trait Parsers {
|
|||
def isOpType(cat: Int) = cat match {
|
||||
case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL |
|
||||
CURRENCY_SYMBOL =>
|
||||
true; case _ => false
|
||||
true; case _ => false
|
||||
}
|
||||
|
||||
/** Returns true if `c` is a dash `-`, a letter, digit, or an underscore `_`. */
|
||||
|
|
@ -118,7 +141,7 @@ trait Parsers {
|
|||
/** Matches a single character that is not a whitespace character. */
|
||||
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
|
||||
|
||||
/** Matches a single whitespace character, as determined by Char.isWhitespace.*/
|
||||
/** Matches a single whitespace character, as determined by Char.isWhitespace. */
|
||||
lazy val SpaceClass = charClass(_.isWhitespace, "whitespace character")
|
||||
|
||||
/** Matches a non-empty String consisting of non-whitespace characters. */
|
||||
|
|
@ -128,21 +151,23 @@ trait Parsers {
|
|||
lazy val OptNotSpace = NotSpaceClass.*.string
|
||||
|
||||
/**
|
||||
* Matches a non-empty String consisting of whitespace characters.
|
||||
* The suggested tab completion is a single, constant space character.
|
||||
* Matches a non-empty String consisting of whitespace characters. The suggested tab completion is
|
||||
* a single, constant space character.
|
||||
*/
|
||||
lazy val Space: Parser[Seq[Char]] = SpaceClass.+.examples(" ")
|
||||
|
||||
/**
|
||||
* Matches a possibly empty String consisting of whitespace characters.
|
||||
* The suggested tab completion is a single, constant space character.
|
||||
* Matches a possibly empty String consisting of whitespace characters. The suggested tab
|
||||
* completion is a single, constant space character.
|
||||
*/
|
||||
lazy val OptSpace = SpaceClass.*.examples(" ")
|
||||
|
||||
/** Parses a non-empty String that contains only valid URI characters, as defined by [[URIChar]].*/
|
||||
/**
|
||||
* Parses a non-empty String that contains only valid URI characters, as defined by [[URIChar]].
|
||||
*/
|
||||
lazy val URIClass = URIChar.+.string !!! "Invalid URI"
|
||||
|
||||
/** Triple-quotes, as used for verbatim quoting.*/
|
||||
/** Triple-quotes, as used for verbatim quoting. */
|
||||
lazy val VerbatimDQuotes = "\"\"\""
|
||||
|
||||
/** Double quote character. */
|
||||
|
|
@ -156,15 +181,21 @@ trait Parsers {
|
|||
|
||||
/** Matches any character except a double quote or whitespace. */
|
||||
lazy val NotDQuoteSpaceClass =
|
||||
charClass({ c: Char =>
|
||||
(c != DQuoteChar) && !c.isWhitespace
|
||||
}, "non-double-quote-space character")
|
||||
charClass(
|
||||
{ c: Char =>
|
||||
(c != DQuoteChar) && !c.isWhitespace
|
||||
},
|
||||
"non-double-quote-space character"
|
||||
)
|
||||
|
||||
/** Matches any character except a double quote or backslash. */
|
||||
lazy val NotDQuoteBackslashClass =
|
||||
charClass({ c: Char =>
|
||||
(c != DQuoteChar) && (c != BackslashChar)
|
||||
}, "non-double-quote-backslash character")
|
||||
charClass(
|
||||
{ c: Char =>
|
||||
(c != DQuoteChar) && (c != BackslashChar)
|
||||
},
|
||||
"non-double-quote-backslash character"
|
||||
)
|
||||
|
||||
/** Matches a single character that is valid somewhere in a URI. */
|
||||
lazy val URIChar = charClass(alphanum, "alphanum") | chars("_-!.~'()*,;:$&+=?/[]@%#")
|
||||
|
|
@ -174,16 +205,21 @@ trait Parsers {
|
|||
('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
|
||||
* directory will be proposed.
|
||||
* @return the file that was parsed from the input string. The returned path may or may not exist.
|
||||
* @param base
|
||||
* the directory used for completion proposals (when the user presses the TAB key). Only paths
|
||||
* under this directory will be proposed.
|
||||
* @return
|
||||
* the file that was parsed from the input string. The returned path may or may not exist.
|
||||
*/
|
||||
def fileParser(base: File): Parser[File] =
|
||||
OptSpace ~> StringBasic
|
||||
.examples(new FileExamples(base))
|
||||
.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>")
|
||||
|
||||
/** Parses a signed integer. */
|
||||
|
|
@ -195,44 +231,49 @@ trait Parsers {
|
|||
private[this] def toInt(neg: Option[Char], digits: Seq[Char]): Int =
|
||||
(neg.toSeq ++ digits).mkString.toInt
|
||||
|
||||
/** Parses the lower-case values `true` and `false` into their corresponding Boolean values. */
|
||||
/** Parses the lower-case values `true` and `false` into their corresponding Boolean values. */
|
||||
lazy val Bool = ("true" ^^^ true) | ("false" ^^^ false)
|
||||
|
||||
/**
|
||||
* Parses a potentially quoted String value. The value may be verbatim quoted ([[StringVerbatim]]),
|
||||
* quoted with interpreted escapes ([[StringEscapable]]), or unquoted ([[NotQuoted]]).
|
||||
* Parses a potentially quoted String value. The value may be verbatim quoted
|
||||
* ([[StringVerbatim]]), quoted with interpreted escapes ([[StringEscapable]]), or unquoted
|
||||
* ([[NotQuoted]]).
|
||||
*/
|
||||
lazy val StringBasic = StringVerbatim | StringEscapable | NotQuoted | NotQuotedThenQuoted
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
lazy val StringVerbatim: Parser[String] = VerbatimDQuotes ~>
|
||||
any.+.string.filter(!_.contains(VerbatimDQuotes), _ => "Invalid verbatim string") <~
|
||||
VerbatimDQuotes
|
||||
|
||||
/**
|
||||
* Parses a string value, interpreting escapes and discarding the surrounding quotes in the result.
|
||||
* See [[EscapeSequence]] for supported escapes.
|
||||
* Parses a string value, interpreting escapes and discarding the surrounding quotes in the
|
||||
* result. See [[EscapeSequence]] for supported escapes.
|
||||
*/
|
||||
lazy val StringEscapable: Parser[String] =
|
||||
(DQuoteChar ~> (NotDQuoteBackslashClass | EscapeSequence).+.string <~ DQuoteChar |
|
||||
(DQuoteChar ~ DQuoteChar) ^^^ "")
|
||||
|
||||
/**
|
||||
* Parses a size unit string. For example, `128K` parsers to `128L * 1024`, and `1.25g` parses
|
||||
* to `1024L * 1024 * 1024 * 5 / 4`.
|
||||
* Parses a size unit string. For example, `128K` parsers to `128L * 1024`, and `1.25g` parses to
|
||||
* `1024L * 1024 * 1024 * 5 / 4`.
|
||||
*/
|
||||
lazy val Size: Parser[Long] = SizeParser.value
|
||||
|
||||
/**
|
||||
* Parses a brace enclosed string and, if each opening brace is matched with a closing brace,
|
||||
* it returns the entire string including the braces.
|
||||
* Parses a brace enclosed string and, if each opening brace is matched with a closing brace, it
|
||||
* returns the entire string including the braces.
|
||||
*
|
||||
* @param open the opening character, e.g. '{'
|
||||
* @param close the closing character, e.g. '}'
|
||||
* @return a parser for the brace encloosed string.
|
||||
* @param open
|
||||
* the opening character, e.g. '{'
|
||||
* @param close
|
||||
* the closing character, e.g. '}'
|
||||
* @return
|
||||
* a parser for the brace encloosed string.
|
||||
*/
|
||||
private[sbt] def braces(open: Char, close: Char): Parser[String] = {
|
||||
val notDelim = charClass(c => c != open && c != close).*.string
|
||||
|
|
@ -240,10 +281,10 @@ trait Parsers {
|
|||
(open ~ (notDelim ~ close).?).flatMap {
|
||||
case (l, Some((content, r))) => Parser.success(s"$l$content$r")
|
||||
case (l, None) =>
|
||||
((notDelim ~ impl()).map {
|
||||
case (leftPrefix, nestedBraces) => leftPrefix + nestedBraces
|
||||
}.+ ~ notDelim ~ close).map {
|
||||
case ((nested, suffix), r) => s"$l${nested.mkString}$suffix$r"
|
||||
((notDelim ~ impl()).map { case (leftPrefix, nestedBraces) =>
|
||||
leftPrefix + nestedBraces
|
||||
}.+ ~ notDelim ~ close).map { case ((nested, suffix), r) =>
|
||||
s"$l${nested.mkString}$suffix$r"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,23 +292,27 @@ trait Parsers {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a single escape sequence into the represented Char.
|
||||
* Escapes start with a backslash and are followed by `u` for a [[UnicodeEscape]] or by `b`, `t`, `n`, `f`, `r`, `"`, `'`, `\` for standard escapes.
|
||||
* Parses a single escape sequence into the represented Char. Escapes start with a backslash and
|
||||
* are followed by `u` for a [[UnicodeEscape]] or by `b`, `t`, `n`, `f`, `r`, `"`, `'`, `\` for
|
||||
* standard escapes.
|
||||
*/
|
||||
lazy val EscapeSequence: Parser[Char] =
|
||||
BackslashChar ~> ('b' ^^^ '\b' | 't' ^^^ '\t' | 'n' ^^^ '\n' | 'f' ^^^ '\f' | 'r' ^^^ '\r' |
|
||||
'\"' ^^^ '\"' | '\'' ^^^ '\'' | '\\' ^^^ '\\' | UnicodeEscape)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
lazy val UnicodeEscape: Parser[Char] =
|
||||
("u" ~> repeat(HexDigit, 4, 4)) map { seq =>
|
||||
Integer.parseInt(seq.mkString, 16).toChar
|
||||
}
|
||||
|
||||
/** Parses an unquoted, non-empty String value that cannot start with a double quote and cannot contain whitespace.*/
|
||||
/**
|
||||
* Parses an unquoted, non-empty String value that cannot start with a double quote and cannot
|
||||
* contain whitespace.
|
||||
*/
|
||||
lazy val NotQuoted = (NotDQuoteSpaceClass ~ OptNotSpace) map { case (c, s) => c.toString + s }
|
||||
|
||||
/** Parses a non-empty String value that cannot start with a double quote, but includes double quotes.*/
|
||||
|
|
@ -276,27 +321,27 @@ trait Parsers {
|
|||
}
|
||||
|
||||
/**
|
||||
* Applies `rep` zero or more times, separated by `sep`.
|
||||
* The result is the (possibly empty) sequence of results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
* Applies `rep` zero or more times, separated by `sep`. The result is the (possibly empty)
|
||||
* sequence of results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
*/
|
||||
def repsep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
|
||||
rep1sep(rep, sep) ?? nilSeq[T]
|
||||
|
||||
/**
|
||||
* Applies `rep` one or more times, separated by `sep`.
|
||||
* The result is the non-empty sequence of results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
* Applies `rep` one or more times, separated by `sep`. The result is the non-empty sequence of
|
||||
* results from the multiple `rep` applications. The `sep` results are discarded.
|
||||
*/
|
||||
def rep1sep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
|
||||
(rep ~ (sep ~> rep).*).map { case (x ~ xs) => x +: xs }
|
||||
|
||||
/** Wraps the result of `p` in `Some`.*/
|
||||
/** Wraps the result of `p` in `Some`. */
|
||||
def some[T](p: Parser[T]): Parser[Option[T]] = p map { v =>
|
||||
Some(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies `f` to the result of `p`, transforming any exception when evaluating
|
||||
* `f` into a parse failure with the exception `toString` as the message.
|
||||
* Applies `f` to the result of `p`, transforming any exception when evaluating `f` into a parse
|
||||
* failure with the exception `toString` as the message.
|
||||
*/
|
||||
def mapOrFail[S, T](p: Parser[S])(f: S => T): Parser[T] =
|
||||
p flatMap { s =>
|
||||
|
|
@ -306,20 +351,24 @@ trait Parsers {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses a space-delimited, possibly empty sequence of arguments.
|
||||
* The arguments may use quotes and escapes according to [[StringBasic]].
|
||||
* Parses a space-delimited, possibly empty sequence of arguments. The arguments may use quotes
|
||||
* and escapes according to [[StringBasic]].
|
||||
*/
|
||||
def spaceDelimited(display: String): Parser[Seq[String]] =
|
||||
(token(Space) ~> token(StringBasic, display)).* <~ SpaceClass.*
|
||||
|
||||
/** Applies `p` and uses `true` as the result if it succeeds and turns failure into a result of `false`. */
|
||||
/**
|
||||
* Applies `p` and uses `true` as the result if it succeeds and turns failure into a result of
|
||||
* `false`.
|
||||
*/
|
||||
def flag[T](p: Parser[T]): Parser[Boolean] = (p ^^^ true) ?? false
|
||||
|
||||
/**
|
||||
* Defines a sequence parser where the parser used for each part depends on the previously parsed values.
|
||||
* `p` is applied to the (possibly empty) sequence of already parsed values to obtain the next parser to use.
|
||||
* The parsers obtained in this way are separated by `sep`, whose result is discarded and only the sequence
|
||||
* of values from the parsers returned by `p` is used for the result.
|
||||
* Defines a sequence parser where the parser used for each part depends on the previously parsed
|
||||
* values. `p` is applied to the (possibly empty) sequence of already parsed values to obtain the
|
||||
* next parser to use. The parsers obtained in this way are separated by `sep`, whose result is
|
||||
* discarded and only the sequence of values from the parsers returned by `p` is used for the
|
||||
* result.
|
||||
*/
|
||||
def repeatDep[A](p: Seq[A] => Parser[A], sep: Parser[Any]): Parser[Seq[A]] = {
|
||||
def loop(acc: Seq[A]): Parser[Seq[A]] = {
|
||||
|
|
@ -339,21 +388,24 @@ trait Parsers {
|
|||
/** Parses a URI that is valid according to the single argument java.net.URI constructor. */
|
||||
lazy val basicUri = mapOrFail(URIClass)(uri => new URI(uri))
|
||||
|
||||
/** Parses a URI that is valid according to the single argument java.net.URI constructor, using `ex` as tab completion examples. */
|
||||
/**
|
||||
* Parses a URI that is valid according to the single argument java.net.URI constructor, using
|
||||
* `ex` as tab completion examples.
|
||||
*/
|
||||
def Uri(ex: Set[URI]) = basicUri examples (ex.map(_.toString))
|
||||
}
|
||||
|
||||
/** Provides standard [[Parser]] implementations. */
|
||||
object Parsers extends Parsers
|
||||
|
||||
/** Provides common [[Parser]] implementations and helper methods.*/
|
||||
/** Provides common [[Parser]] implementations and helper methods. */
|
||||
object DefaultParsers extends Parsers with ParserMain {
|
||||
|
||||
/** Applies parser `p` to input `s` and returns `true` if the parse was successful. */
|
||||
def matches(p: Parser[_], s: String): Boolean =
|
||||
apply(p)(s).resultEmpty.isValid
|
||||
|
||||
/** Returns `true` if `s` parses successfully according to [[ID]].*/
|
||||
/** Returns `true` if `s` parses successfully according to [[ID]]. */
|
||||
def validID(s: String): Boolean = {
|
||||
// Handwritten version of `matches(ID, s)` because validID turned up in profiling.
|
||||
def isIdChar(c: Char): Boolean = Character.isLetterOrDigit(c) || (c == '-') || (c == '_')
|
||||
|
|
|
|||
|
|
@ -44,13 +44,12 @@ private[sbt] object SizeParser {
|
|||
((numberParser <~ SpaceClass
|
||||
.examples(" ", "b", "B", "g", "G", "k", "K", "m", "M")
|
||||
.*) ~ unitParser.?)
|
||||
.map {
|
||||
case (number, unit) =>
|
||||
unit match {
|
||||
case None | Some(Bytes) => multiply(number, right = 1L)
|
||||
case Some(KiloBytes) => multiply(number, right = 1024L)
|
||||
case Some(MegaBytes) => multiply(number, right = 1024L * 1024)
|
||||
case Some(GigaBytes) => multiply(number, right = 1024L * 1024 * 1024)
|
||||
}
|
||||
.map { case (number, unit) =>
|
||||
unit match {
|
||||
case None | Some(Bytes) => multiply(number, right = 1L)
|
||||
case Some(KiloBytes) => multiply(number, right = 1024L)
|
||||
case Some(MegaBytes) => multiply(number, right = 1024L * 1024)
|
||||
case Some(GigaBytes) => multiply(number, right = 1024L * 1024 * 1024)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import DefaultParsers._
|
|||
import TypeString._
|
||||
|
||||
/**
|
||||
* Basic representation of types parsed from Manifest.toString.
|
||||
* This can only represent the structure of parameterized types.
|
||||
* All other types are represented by a TypeString with an empty `args`.
|
||||
* Basic representation of types parsed from Manifest.toString. This can only represent the
|
||||
* structure of parameterized types. All other types are represented by a TypeString with an empty
|
||||
* `args`.
|
||||
*/
|
||||
private[sbt] final class TypeString(val base: String, val args: List[TypeString]) {
|
||||
override def toString =
|
||||
|
|
@ -28,7 +28,7 @@ private[sbt] final class TypeString(val base: String, val args: List[TypeString]
|
|||
|
||||
private[sbt] object TypeString {
|
||||
|
||||
/** Makes the string representation of a type as returned by Manifest.toString more readable.*/
|
||||
/** Makes the string representation of a type as returned by Manifest.toString more readable. */
|
||||
def cleanup(typeString: String): String =
|
||||
parse(typeString, typeStringParser) match {
|
||||
case Right(ts) => ts.toString
|
||||
|
|
@ -36,19 +36,19 @@ private[sbt] object TypeString {
|
|||
}
|
||||
|
||||
/**
|
||||
* Makes a fully qualified type name provided by Manifest.toString more readable.
|
||||
* The argument should be just a name (like scala.Tuple2) and not a full type (like scala.Tuple2[Int,Boolean])
|
||||
* Makes a fully qualified type name provided by Manifest.toString more readable. The argument
|
||||
* should be just a name (like scala.Tuple2) and not a full type (like scala.Tuple2[Int,Boolean])
|
||||
*/
|
||||
def cleanupTypeName(base: String): String =
|
||||
dropPrefix(base).replace('$', '.')
|
||||
|
||||
/**
|
||||
* Removes prefixes from a fully qualified type name that are unnecessary in the presence of standard imports for an sbt setting.
|
||||
* This does not use the compiler and is therefore a conservative approximation.
|
||||
* Removes prefixes from a fully qualified type name that are unnecessary in the presence of
|
||||
* standard imports for an sbt setting. This does not use the compiler and is therefore a
|
||||
* conservative approximation.
|
||||
*/
|
||||
def dropPrefix(base: String): String =
|
||||
if (base.startsWith(SbtPrefix))
|
||||
base.substring(SbtPrefix.length)
|
||||
if (base.startsWith(SbtPrefix)) base.substring(SbtPrefix.length)
|
||||
else if (base.startsWith(CollectionPrefix)) {
|
||||
val simple = base.substring(CollectionPrefix.length)
|
||||
if (ShortenCollection(simple)) simple else base
|
||||
|
|
@ -75,8 +75,9 @@ private[sbt] object TypeString {
|
|||
)
|
||||
|
||||
/**
|
||||
* A Parser that extracts basic structure from the string representation of a type from Manifest.toString.
|
||||
* This is rudimentary and essentially only decomposes the string into names and arguments for parameterized types.
|
||||
* A Parser that extracts basic structure from the string representation of a type from
|
||||
* Manifest.toString. This is rudimentary and essentially only decomposes the string into names
|
||||
* and arguments for parameterized types.
|
||||
*/
|
||||
lazy val typeStringParser: Parser[TypeString] = {
|
||||
def isFullScalaIDChar(c: Char) = isScalaIDChar(c) || c == '.' || c == '$'
|
||||
|
|
|
|||
|
|
@ -10,22 +10,23 @@ package complete
|
|||
|
||||
sealed trait UpperBound {
|
||||
|
||||
/** True if and only if the given value meets this bound.*/
|
||||
/** True if and only if the given value meets this bound. */
|
||||
def >=(min: Int): Boolean
|
||||
|
||||
/** True if and only if this bound is one.*/
|
||||
/** True if and only if this bound is one. */
|
||||
def isOne: Boolean
|
||||
|
||||
/** True if and only if this bound is zero.*/
|
||||
/** True if and only if this bound is zero. */
|
||||
def isZero: Boolean
|
||||
|
||||
/**
|
||||
* If this bound is zero or Infinite, `decrement` returns this bound.
|
||||
* Otherwise, this bound is finite and greater than zero and `decrement` returns the bound that is one less than this bound.
|
||||
* If this bound is zero or Infinite, `decrement` returns this bound. Otherwise, this bound is
|
||||
* finite and greater than zero and `decrement` returns the bound that is one less than this
|
||||
* bound.
|
||||
*/
|
||||
def decrement: UpperBound
|
||||
|
||||
/** True if and only if this is unbounded.*/
|
||||
/** True if and only if this is unbounded. */
|
||||
def isInfinite: Boolean
|
||||
|
||||
}
|
||||
|
|
@ -45,8 +46,8 @@ case object Infinite extends UpperBound {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents a finite upper bound. The maximum allowed value is 'value', inclusive.
|
||||
* It must positive.
|
||||
* Represents a finite upper bound. The maximum allowed value is 'value', inclusive. It must
|
||||
* positive.
|
||||
*/
|
||||
final case class Finite(value: Int) extends UpperBound {
|
||||
assume(value >= 0, "Maximum occurrences must be nonnegative.")
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ import org.scalacheck._, Gen._, Prop._
|
|||
object DefaultParsersSpec extends Properties("DefaultParsers") {
|
||||
import DefaultParsers.{ ID, isIDChar, matches, validID }
|
||||
|
||||
property("∀ s ∈ String: validID(s) == matches(ID, s)") = forAll(
|
||||
(s: String) => validID(s) == matches(ID, s)
|
||||
)
|
||||
property("∀ s ∈ String: validID(s) == matches(ID, s)") =
|
||||
forAll((s: String) => validID(s) == matches(ID, s))
|
||||
|
||||
property("∀ s ∈ genID: matches(ID, s)") = forAll(genID)(s => matches(ID, s))
|
||||
property("∀ s ∈ genID: validID(s)") = forAll(genID)(s => validID(s))
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ class FileExamplesTest extends UnitSpec {
|
|||
|
||||
"listing all files in an absolute base directory" should
|
||||
"produce the entire base directory's contents" in {
|
||||
withDirectoryStructure() { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.allRelativizedPaths)
|
||||
withDirectoryStructure() { ds =>
|
||||
ds.fileExamples().toList should contain theSameElementsAs (ds.allRelativizedPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"listing files with a prefix that matches none" should "produce an empty list" in {
|
||||
withDirectoryStructure(withCompletionPrefix = "z") { ds =>
|
||||
|
|
|
|||
|
|
@ -14,57 +14,57 @@ class ParserWithExamplesTest extends UnitSpec {
|
|||
|
||||
"listing a limited number of completions" should
|
||||
"grab only the needed number of elements from the iterable source of examples" in {
|
||||
val _ = new ParserWithLazyExamples {
|
||||
parserWithExamples.completions(0)
|
||||
examples.size shouldEqual maxNumberOfExamples
|
||||
val _ = new ParserWithLazyExamples {
|
||||
parserWithExamples.completions(0)
|
||||
examples.size shouldEqual maxNumberOfExamples
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"listing only valid completions" should
|
||||
"use the delegate parser to remove invalid examples" in {
|
||||
val _ = new ParserWithValidExamples {
|
||||
val validCompletions = Completions(
|
||||
Set(
|
||||
suggestion("blue"),
|
||||
suggestion("red")
|
||||
val _ = new ParserWithValidExamples {
|
||||
val validCompletions = Completions(
|
||||
Set(
|
||||
suggestion("blue"),
|
||||
suggestion("red")
|
||||
)
|
||||
)
|
||||
)
|
||||
parserWithExamples.completions(0) shouldEqual validCompletions
|
||||
parserWithExamples.completions(0) shouldEqual validCompletions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid completions in a derived parser" should
|
||||
"produce only valid examples that start with the character of the derivation" in {
|
||||
val _ = new ParserWithValidExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue")
|
||||
val _ = new ParserWithValidExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue")
|
||||
)
|
||||
)
|
||||
)
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid and invalid completions" should
|
||||
"produce the entire source of examples" in {
|
||||
val _ = new parserWithAllExamples {
|
||||
val completions = Completions(examples.map(suggestion(_)).toSet)
|
||||
parserWithExamples.completions(0) shouldEqual completions
|
||||
val _ = new parserWithAllExamples {
|
||||
val completions = Completions(examples.map(suggestion(_)).toSet)
|
||||
parserWithExamples.completions(0) shouldEqual completions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"listing valid and invalid completions in a derived parser" should
|
||||
"produce only examples that start with the character of the derivation" in {
|
||||
val _ = new parserWithAllExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue"),
|
||||
suggestion("lock")
|
||||
val _ = new parserWithAllExamples {
|
||||
val derivedCompletions = Completions(
|
||||
Set(
|
||||
suggestion("lue"),
|
||||
suggestion("lock")
|
||||
)
|
||||
)
|
||||
)
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ParserWithLazyExamples
|
||||
extends ParserExample(
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ package com.github.ghik.silencer
|
|||
import scala.annotation.Annotation
|
||||
|
||||
/**
|
||||
* When silencer compiler plugin is enabled, this annotation suppresses all warnings emitted by scalac for some portion
|
||||
* of source code. It can be applied on any definition (`class`, def`, `val`, `var`, etc.) or on arbitrary expression,
|
||||
* e.g. {123; 456}: @silent`
|
||||
* When silencer compiler plugin is enabled, this annotation suppresses all warnings emitted by
|
||||
* scalac for some portion of source code. It can be applied on any definition (`class`, def`,
|
||||
* `val`, `var`, etc.) or on arbitrary expression, e.g. {123; 456}: @silent`
|
||||
*/
|
||||
class silent extends Annotation
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ package sbt.internal.util
|
|||
|
||||
import sbt.util._
|
||||
|
||||
/** Implements the level-setting methods of Logger.*/
|
||||
/** Implements the level-setting methods of Logger. */
|
||||
abstract class BasicLogger extends AbstractLogger {
|
||||
private var traceEnabledVar: Int = java.lang.Integer.MAX_VALUE
|
||||
private var level: Level.Value = Level.Info
|
||||
|
|
|
|||
|
|
@ -29,11 +29,10 @@ object BufferedAppender {
|
|||
}
|
||||
|
||||
/**
|
||||
* An appender that can buffer the logging done on it and then can flush the buffer
|
||||
* to the delegate appender provided in the constructor. Use 'record()' to
|
||||
* start buffering and then 'play' to flush the buffer to the backing appender.
|
||||
* The logging level set at the time a message is originally logged is used, not
|
||||
* the level at the time 'play' is called.
|
||||
* An appender that can buffer the logging done on it and then can flush the buffer to the delegate
|
||||
* appender provided in the constructor. Use 'record()' to start buffering and then 'play' to flush
|
||||
* the buffer to the backing appender. The logging level set at the time a message is originally
|
||||
* logged is used, not the level at the time 'play' is called.
|
||||
*/
|
||||
class BufferedAppender(override val name: String, delegate: Appender) extends Appender {
|
||||
override def close(): Unit = log4j.get match {
|
||||
|
|
@ -108,8 +107,8 @@ class BufferedAppender(override val name: String, delegate: Appender) extends Ap
|
|||
}
|
||||
|
||||
/**
|
||||
* Flushes the buffer to the delegate logger. This method calls logAll on the delegate
|
||||
* so that the messages are written consecutively. The buffer is cleared in the process.
|
||||
* Flushes the buffer to the delegate logger. This method calls logAll on the delegate so that the
|
||||
* messages are written consecutively. The buffer is cleared in the process.
|
||||
*/
|
||||
def play(): Unit =
|
||||
synchronized {
|
||||
|
|
@ -131,11 +130,10 @@ class BufferedAppender(override val name: String, delegate: Appender) extends Ap
|
|||
}
|
||||
|
||||
/**
|
||||
* A logger that can buffer the logging done on it and then can flush the buffer
|
||||
* to the delegate logger provided in the constructor. Use 'startRecording' to
|
||||
* start buffering and then 'play' from to flush the buffer to the backing logger.
|
||||
* The logging level set at the time a message is originally logged is used, not
|
||||
* the level at the time 'play' is called.
|
||||
* A logger that can buffer the logging done on it and then can flush the buffer to the delegate
|
||||
* logger provided in the constructor. Use 'startRecording' to start buffering and then 'play' from
|
||||
* to flush the buffer to the backing logger. The logging level set at the time a message is
|
||||
* originally logged is used, not the level at the time 'play' is called.
|
||||
*
|
||||
* This class assumes that it is the only client of the delegate logger.
|
||||
*/
|
||||
|
|
@ -168,8 +166,8 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
|
|||
}
|
||||
|
||||
/**
|
||||
* Flushes the buffer to the delegate logger. This method calls logAll on the delegate
|
||||
* so that the messages are written consecutively. The buffer is cleared in the process.
|
||||
* Flushes the buffer to the delegate logger. This method calls logAll on the delegate so that the
|
||||
* messages are written consecutively. The buffer is cleared in the process.
|
||||
*/
|
||||
def play(): Unit = synchronized { delegate.logAll(buffer.toList); buffer.clear() }
|
||||
|
||||
|
|
|
|||
|
|
@ -40,27 +40,36 @@ object ConsoleLogger {
|
|||
/**
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*
|
||||
* @param out Where to log the messages.
|
||||
* @return A new `ConsoleLogger` that logs to `out`.
|
||||
* @param out
|
||||
* Where to log the messages.
|
||||
* @return
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*/
|
||||
def apply(out: PrintStream): ConsoleLogger = apply(ConsoleOut.printStreamOut(out))
|
||||
|
||||
/**
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*
|
||||
* @param out Where to log the messages.
|
||||
* @return A new `ConsoleLogger` that logs to `out`.
|
||||
* @param out
|
||||
* Where to log the messages.
|
||||
* @return
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*/
|
||||
def apply(out: PrintWriter): ConsoleLogger = apply(ConsoleOut.printWriterOut(out))
|
||||
|
||||
/**
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*
|
||||
* @param out Where to log the messages.
|
||||
* @param ansiCodesSupported `true` if `out` supported ansi codes, `false` otherwise.
|
||||
* @param useFormat `true` to show formatting, `false` to remove it from messages.
|
||||
* @param suppressedMessage How to show suppressed stack traces.
|
||||
* @return A new `ConsoleLogger` that logs to `out`.
|
||||
* @param out
|
||||
* Where to log the messages.
|
||||
* @param ansiCodesSupported
|
||||
* `true` if `out` supported ansi codes, `false` otherwise.
|
||||
* @param useFormat
|
||||
* `true` to show formatting, `false` to remove it from messages.
|
||||
* @param suppressedMessage
|
||||
* How to show suppressed stack traces.
|
||||
* @return
|
||||
* A new `ConsoleLogger` that logs to `out`.
|
||||
*/
|
||||
def apply(
|
||||
out: ConsoleOut = ConsoleOut.systemOut,
|
||||
|
|
@ -73,8 +82,7 @@ object ConsoleLogger {
|
|||
}
|
||||
|
||||
/**
|
||||
* A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
* A logger that logs to the console. On supported systems, the level labels are colored.
|
||||
*/
|
||||
class ConsoleLogger private[ConsoleLogger] (
|
||||
out: ConsoleOut,
|
||||
|
|
@ -144,10 +152,9 @@ object ConsoleAppender {
|
|||
|
||||
/**
|
||||
* Indicates whether formatting has been disabled in environment variables.
|
||||
* 1. -Dsbt.log.noformat=true means no formatting.
|
||||
* 2. -Dsbt.color=always/auto/never/true/false
|
||||
* 3. -Dsbt.colour=always/auto/never/true/false
|
||||
* 4. -Dsbt.log.format=always/auto/never/true/false
|
||||
* 1. -Dsbt.log.noformat=true means no formatting. 2. -Dsbt.color=always/auto/never/true/false
|
||||
* 3. -Dsbt.colour=always/auto/never/true/false 4.
|
||||
* -Dsbt.log.format=always/auto/never/true/false
|
||||
*/
|
||||
@deprecated("Use Terminal.isAnsiSupported or Terminal.isColorEnabled", "1.4.0")
|
||||
lazy val formatEnabledInEnv: Boolean = Terminal.isAnsiSupported
|
||||
|
|
@ -163,58 +170,74 @@ object ConsoleAppender {
|
|||
/**
|
||||
* A new `ConsoleAppender` that writes to standard output.
|
||||
*
|
||||
* @return A new `ConsoleAppender` that writes to standard output.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to standard output.
|
||||
*/
|
||||
def apply(): Appender = apply(ConsoleOut.systemOut)
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` that appends log message to `out`.
|
||||
*
|
||||
* @param out Where to write messages.
|
||||
* @return A new `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @return
|
||||
* A new `ConsoleAppender`.
|
||||
*/
|
||||
def apply(out: PrintStream): Appender = apply(ConsoleOut.printStreamOut(out))
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` that appends log messages to `out`.
|
||||
*
|
||||
* @param out Where to write messages.
|
||||
* @return A new `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @return
|
||||
* A new `ConsoleAppender`.
|
||||
*/
|
||||
def apply(out: PrintWriter): Appender = apply(ConsoleOut.printWriterOut(out))
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*
|
||||
* @param out Where to write messages.
|
||||
* @return A new `ConsoleAppender that writes to `out`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @return
|
||||
* A new `ConsoleAppender that writes to `out`.
|
||||
*/
|
||||
def apply(out: ConsoleOut): Appender = apply(generateName(), out)
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to standard output.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @return A new `ConsoleAppender` that writes to standard output.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to standard output.
|
||||
*/
|
||||
def apply(name: String): Appender = apply(name, ConsoleOut.systemOut)
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param out Where to write messages.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(name: String, out: ConsoleOut): Appender = apply(name, out, Terminal.isAnsiSupported)
|
||||
|
||||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param out Where to write messages.
|
||||
* @param suppressedMessage How to handle stack traces.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @param suppressedMessage
|
||||
* How to handle stack traces.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(
|
||||
name: String,
|
||||
|
|
@ -228,10 +251,14 @@ object ConsoleAppender {
|
|||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param out Where to write messages.
|
||||
* @param useFormat `true` to enable format (color, bold, etc.), `false` to remove formatting.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @param useFormat
|
||||
* `true` to enable format (color, bold, etc.), `false` to remove formatting.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(name: String, out: ConsoleOut, useFormat: Boolean): Appender =
|
||||
apply(name, out, useFormat || Terminal.isAnsiSupported, useFormat, noSuppressedMessage)
|
||||
|
|
@ -239,9 +266,12 @@ object ConsoleAppender {
|
|||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param terminal The terminal to which this appender corresponds
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param terminal
|
||||
* The terminal to which this appender corresponds
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(name: String, terminal: Terminal): Appender = {
|
||||
new ConsoleAppender(name, Properties.from(terminal), noSuppressedMessage)
|
||||
|
|
@ -262,10 +292,14 @@ object ConsoleAppender {
|
|||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param terminal The terminal to which this appender corresponds
|
||||
* @param suppressedMessage How to handle stack traces.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param terminal
|
||||
* The terminal to which this appender corresponds
|
||||
* @param suppressedMessage
|
||||
* How to handle stack traces.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(
|
||||
name: String,
|
||||
|
|
@ -278,12 +312,16 @@ object ConsoleAppender {
|
|||
/**
|
||||
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
|
||||
*
|
||||
* @param name An identifier for the `ConsoleAppender`.
|
||||
* @param out Where to write messages.
|
||||
* @param ansiCodesSupported `true` if the output stream supports ansi codes, `false` otherwise.
|
||||
* @param useFormat `true` to enable format (color, bold, etc.), `false` to remove
|
||||
* formatting.
|
||||
* @return A new `ConsoleAppender` that writes to `out`.
|
||||
* @param name
|
||||
* An identifier for the `ConsoleAppender`.
|
||||
* @param out
|
||||
* Where to write messages.
|
||||
* @param ansiCodesSupported
|
||||
* `true` if the output stream supports ansi codes, `false` otherwise.
|
||||
* @param useFormat
|
||||
* `true` to enable format (color, bold, etc.), `false` to remove formatting.
|
||||
* @return
|
||||
* A new `ConsoleAppender` that writes to `out`.
|
||||
*/
|
||||
def apply(
|
||||
name: String,
|
||||
|
|
@ -302,8 +340,10 @@ object ConsoleAppender {
|
|||
/**
|
||||
* Converts the Log4J `level` to the corresponding sbt level.
|
||||
*
|
||||
* @param level A level, as represented by Log4J.
|
||||
* @return The corresponding level in sbt's world.
|
||||
* @param level
|
||||
* A level, as represented by Log4J.
|
||||
* @return
|
||||
* The corresponding level in sbt's world.
|
||||
*/
|
||||
def toLevel(level: XLevel): Level.Value =
|
||||
level match {
|
||||
|
|
@ -319,8 +359,10 @@ object ConsoleAppender {
|
|||
/**
|
||||
* Converts the sbt `level` to the corresponding Log4J level.
|
||||
*
|
||||
* @param level A level, as represented by sbt.
|
||||
* @return The corresponding level in Log4J's world.
|
||||
* @param level
|
||||
* A level, as represented by sbt.
|
||||
* @return
|
||||
* The corresponding level in Log4J's world.
|
||||
*/
|
||||
def toXLevel(level: Level.Value): XLevel =
|
||||
level match {
|
||||
|
|
@ -341,8 +383,7 @@ object ConsoleAppender {
|
|||
// https://logging.apache.org/log4j/2.x/log4j-core/apidocs/index.html
|
||||
|
||||
/**
|
||||
* A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
* A logger that logs to the console. On supported systems, the level labels are colored.
|
||||
*
|
||||
* This logger is not thread-safe.
|
||||
*/
|
||||
|
|
@ -357,12 +398,17 @@ class ConsoleAppender(
|
|||
log4j.synchronized {
|
||||
log4j.get match {
|
||||
case null =>
|
||||
val l = new Log4JConsoleAppender(name, properties, suppressedMessage, { event =>
|
||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||
val message = event.getMessage
|
||||
try appendMessage(level, message)
|
||||
catch { case _: ClosedChannelException => }
|
||||
})
|
||||
val l = new Log4JConsoleAppender(
|
||||
name,
|
||||
properties,
|
||||
suppressedMessage,
|
||||
{ event =>
|
||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||
val message = event.getMessage
|
||||
try appendMessage(level, message)
|
||||
catch { case _: ClosedChannelException => }
|
||||
}
|
||||
)
|
||||
log4j.set(l)
|
||||
l
|
||||
case l => l
|
||||
|
|
@ -404,11 +450,13 @@ trait Appender extends AutoCloseable {
|
|||
/**
|
||||
* Logs the stack trace of `t`, possibly shortening it.
|
||||
*
|
||||
* The `traceLevel` parameter configures how the stack trace will be shortened.
|
||||
* See `StackTrace.trimmed`.
|
||||
* The `traceLevel` parameter configures how the stack trace will be shortened. See
|
||||
* `StackTrace.trimmed`.
|
||||
*
|
||||
* @param t The `Throwable` whose stack trace to log.
|
||||
* @param traceLevel How to shorten the stack trace.
|
||||
* @param t
|
||||
* The `Throwable` whose stack trace to log.
|
||||
* @param traceLevel
|
||||
* How to shorten the stack trace.
|
||||
*/
|
||||
def trace(t: => Throwable, traceLevel: Int): Unit = {
|
||||
if (traceLevel >= 0)
|
||||
|
|
@ -423,8 +471,10 @@ trait Appender extends AutoCloseable {
|
|||
/**
|
||||
* Logs a `ControlEvent` to the log.
|
||||
*
|
||||
* @param event The kind of `ControlEvent`.
|
||||
* @param message The message to log.
|
||||
* @param event
|
||||
* The kind of `ControlEvent`.
|
||||
* @param message
|
||||
* The message to log.
|
||||
*/
|
||||
def control(event: ControlEvent.Value, message: => String): Unit =
|
||||
appendLog(labelColor(Level.Info), Level.Info.toString, BLUE, message)
|
||||
|
|
@ -432,8 +482,10 @@ trait Appender extends AutoCloseable {
|
|||
/**
|
||||
* Appends the message `message` to the to the log at level `level`.
|
||||
*
|
||||
* @param level The importance level of the message.
|
||||
* @param message The message to log.
|
||||
* @param level
|
||||
* The importance level of the message.
|
||||
* @param message
|
||||
* The message to log.
|
||||
*/
|
||||
def appendLog(level: Level.Value, message: => String): Unit = {
|
||||
appendLog(labelColor(level), level.toString, NO_COLOR, message)
|
||||
|
|
@ -442,8 +494,10 @@ trait Appender extends AutoCloseable {
|
|||
/**
|
||||
* Select the right color for the label given `level`.
|
||||
*
|
||||
* @param level The label to consider to select the color.
|
||||
* @return The color to use to color the label.
|
||||
* @param level
|
||||
* The label to consider to select the color.
|
||||
* @return
|
||||
* The color to use to color the label.
|
||||
*/
|
||||
private def labelColor(level: Level.Value): String =
|
||||
level match {
|
||||
|
|
@ -457,11 +511,14 @@ trait Appender extends AutoCloseable {
|
|||
* `labelColor` if formatting is enabled. The lines of the messages are colored with
|
||||
* `messageColor` if formatting is enabled.
|
||||
*
|
||||
* @param labelColor The color to use to format the label.
|
||||
* @param label The label to prefix each line with. The label is shown between square
|
||||
* brackets.
|
||||
* @param messageColor The color to use to format the message.
|
||||
* @param message The message to write.
|
||||
* @param labelColor
|
||||
* The color to use to format the label.
|
||||
* @param label
|
||||
* The label to prefix each line with. The label is shown between square brackets.
|
||||
* @param messageColor
|
||||
* The color to use to format the message.
|
||||
* @param message
|
||||
* The message to write.
|
||||
*/
|
||||
private def appendLog(
|
||||
labelColor: String,
|
||||
|
|
@ -535,7 +592,9 @@ trait Appender extends AutoCloseable {
|
|||
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
|
||||
}
|
||||
if (traceLevel <= 2) {
|
||||
suppressedMessage(new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)) foreach {
|
||||
suppressedMessage(
|
||||
new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
|
||||
) foreach {
|
||||
appendLog(Level.Error, _)
|
||||
}
|
||||
}
|
||||
|
|
@ -545,7 +604,7 @@ trait Appender extends AutoCloseable {
|
|||
def appendEvent(oe: ObjectEvent[_]): Unit = {
|
||||
val contentType = oe.contentType
|
||||
contentType match {
|
||||
case "sbt.internal.util.TraceEvent" => appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
case "sbt.internal.util.TraceEvent" => appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
case "sbt.internal.util.ProgressEvent" =>
|
||||
case _ =>
|
||||
LogExchange.stringCodec[AnyRef](contentType) match {
|
||||
|
|
@ -597,7 +656,7 @@ private[sbt] class ConsoleAppenderFromLog4J(
|
|||
delegate.append(new AbstractLogEvent {
|
||||
override def getLevel(): XLevel = ConsoleAppender.toXLevel(level)
|
||||
override def getMessage(): Message =
|
||||
StringFormatterMessageFactory.INSTANCE.newMessage(message.toString, Array.empty)
|
||||
StringFormatterMessageFactory.INSTANCE.newMessage(message.toString, Array.empty[AnyRef])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ object ConsoleOut {
|
|||
private[this] final val OverwriteLine = "\u001B[A\r\u001B[2K"
|
||||
|
||||
/**
|
||||
* ConsoleOut instance that is backed by System.out. It overwrites the previously printed line
|
||||
* if the function `f(lineToWrite, previousLine)` returns true.
|
||||
* ConsoleOut instance that is backed by System.out. It overwrites the previously printed line if
|
||||
* the function `f(lineToWrite, previousLine)` returns true.
|
||||
*
|
||||
* The ConsoleOut returned by this method assumes that the only newlines are from println calls
|
||||
* and not in the String arguments.
|
||||
*/
|
||||
def systemOutOverwrite(f: (String, String) => Boolean): ConsoleOut = new ConsoleOut {
|
||||
val lockObject = System.out
|
||||
val lockObject: PrintStream = System.out
|
||||
private[this] var last: Option[String] = None
|
||||
private[this] var current = new java.lang.StringBuffer
|
||||
def print(s: String): Unit = synchronized { current.append(s); () }
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ object EscHelpers {
|
|||
final val ESC = '\u001B'
|
||||
|
||||
/**
|
||||
* An escape terminator is a character in the range `@` (decimal value 64) to `~` (decimal value 126).
|
||||
* It is the final character in an escape sequence.
|
||||
* An escape terminator is a character in the range `@` (decimal value 64) to `~` (decimal value
|
||||
* 126). It is the final character in an escape sequence.
|
||||
*
|
||||
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
|
||||
*/
|
||||
|
|
@ -29,10 +29,11 @@ object EscHelpers {
|
|||
*
|
||||
* see: http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
*
|
||||
* The CSI (control sequence instruction) codes start with ESC + '['. This is for testing the second character.
|
||||
* The CSI (control sequence instruction) codes start with ESC + '['. This is for testing the
|
||||
* second character.
|
||||
*
|
||||
* There is an additional CSI (one character) that we could test for, but is not frequnetly used, and we don't
|
||||
* check for it.
|
||||
* There is an additional CSI (one character) that we could test for, but is not frequnetly used,
|
||||
* and we don't check for it.
|
||||
*
|
||||
* cf. http://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes
|
||||
*/
|
||||
|
|
@ -55,13 +56,13 @@ object EscHelpers {
|
|||
s.indexOf(ESC) >= 0
|
||||
|
||||
/**
|
||||
* Returns the string `s` with escape sequences removed.
|
||||
* An escape sequence starts with the ESC character (decimal value 27) and ends with an escape terminator.
|
||||
* @see isEscapeTerminator
|
||||
* Returns the string `s` with escape sequences removed. An escape sequence starts with the ESC
|
||||
* character (decimal value 27) and ends with an escape terminator.
|
||||
* @see
|
||||
* isEscapeTerminator
|
||||
*/
|
||||
def removeEscapeSequences(s: String): String =
|
||||
if (s.isEmpty || !hasEscapeSequence(s))
|
||||
s
|
||||
if (s.isEmpty || !hasEscapeSequence(s)) s
|
||||
else {
|
||||
val sb = new java.lang.StringBuilder
|
||||
nextESC(s, 0, sb)
|
||||
|
|
@ -130,12 +131,15 @@ object EscHelpers {
|
|||
/**
|
||||
* Strips ansi escape and color codes from an input string.
|
||||
*
|
||||
* @param bytes the input bytes
|
||||
* @param stripAnsi toggles whether or not to remove general ansi escape codes
|
||||
* @param stripColor toggles whether or not to remove ansi color codes
|
||||
* @return a string with the escape and color codes removed depending on the input
|
||||
* parameter along with the length of the output string (which may be smaller than
|
||||
* the returned array)
|
||||
* @param bytes
|
||||
* the input bytes
|
||||
* @param stripAnsi
|
||||
* toggles whether or not to remove general ansi escape codes
|
||||
* @param stripColor
|
||||
* toggles whether or not to remove ansi color codes
|
||||
* @return
|
||||
* a string with the escape and color codes removed depending on the input parameter along with
|
||||
* the length of the output string (which may be smaller than the returned array)
|
||||
*/
|
||||
def strip(bytes: Array[Byte], stripAnsi: Boolean, stripColor: Boolean): (Array[Byte], Int) = {
|
||||
val res = Array.fill[Byte](bytes.length)(0)
|
||||
|
|
@ -186,15 +190,17 @@ object EscHelpers {
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes the ansi escape sequences from a string and makes a best attempt at
|
||||
* calculating any ansi moves by hand. For example, if the string contains
|
||||
* a backspace character followed by a character, the output string would
|
||||
* replace the character preceding the backspaces with the character proceding it.
|
||||
* This is in contrast to `strip` which just removes all ansi codes entirely.
|
||||
* Removes the ansi escape sequences from a string and makes a best attempt at calculating any
|
||||
* ansi moves by hand. For example, if the string contains a backspace character followed by a
|
||||
* character, the output string would replace the character preceding the backspaces with the
|
||||
* character proceding it. This is in contrast to `strip` which just removes all ansi codes
|
||||
* entirely.
|
||||
*
|
||||
* @param s the input string
|
||||
* @return a string containing the original characters of the input stream with
|
||||
* the ansi escape codes removed.
|
||||
* @param s
|
||||
* the input string
|
||||
* @return
|
||||
* a string containing the original characters of the input stream with the ansi escape codes
|
||||
* removed.
|
||||
*/
|
||||
def stripColorsAndMoves(s: String): String = {
|
||||
val bytes = s.getBytes
|
||||
|
|
@ -239,7 +245,10 @@ object EscHelpers {
|
|||
new String(res, 0, limit)
|
||||
}
|
||||
|
||||
/** Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after the ESC that starts the sequence. */
|
||||
/**
|
||||
* Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after
|
||||
* the ESC that starts the sequence.
|
||||
*/
|
||||
private[this] def skipESC(s: String, i: Int): Int = {
|
||||
if (i >= s.length) {
|
||||
i
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ import sbt.util._
|
|||
import scala.annotation.nowarn
|
||||
|
||||
/**
|
||||
* A filter logger is used to delegate messages but not the logging level to another logger. This means
|
||||
* that messages are logged at the higher of the two levels set by this logger and its delegate.
|
||||
* A filter logger is used to delegate messages but not the logging level to another logger. This
|
||||
* means that messages are logged at the higher of the two levels set by this logger and its
|
||||
* delegate.
|
||||
*/
|
||||
class FilterLogger(delegate: AbstractLogger) extends BasicLogger {
|
||||
@nowarn override lazy val ansiCodesSupported = delegate.ansiCodesSupported
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import java.io.{ File, PrintWriter }
|
|||
/**
|
||||
* Provides the current global logging configuration.
|
||||
*
|
||||
* `full` is the current global logger. It should not be set directly because it is generated as needed from `backing.newLogger`.
|
||||
* `console` is where all logging from all ConsoleLoggers should go.
|
||||
* `backed` is the Logger that other loggers should feed into.
|
||||
* `backing` tracks the files that persist the global logging.
|
||||
* `newLogger` creates a new global logging configuration from a sink and backing configuration.
|
||||
* `full` is the current global logger. It should not be set directly because it is generated as
|
||||
* needed from `backing.newLogger`. `console` is where all logging from all ConsoleLoggers should
|
||||
* go. `backed` is the Logger that other loggers should feed into. `backing` tracks the files that
|
||||
* persist the global logging. `newLogger` creates a new global logging configuration from a sink
|
||||
* and backing configuration.
|
||||
*/
|
||||
final case class GlobalLogging(
|
||||
full: ManagedLogger,
|
||||
|
|
@ -36,21 +36,24 @@ final case class GlobalLogging1(
|
|||
)
|
||||
|
||||
/**
|
||||
* Tracks the files that persist the global logging.
|
||||
* `file` is the current backing file. `last` is the previous backing file, if there is one.
|
||||
* `newBackingFile` creates a new temporary location for the next backing file.
|
||||
* Tracks the files that persist the global logging. `file` is the current backing file. `last` is
|
||||
* the previous backing file, if there is one. `newBackingFile` creates a new temporary location for
|
||||
* the next backing file.
|
||||
*/
|
||||
final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile: () => File) {
|
||||
|
||||
/** Shifts the current backing file to `last` and sets the current backing to `newFile`. */
|
||||
def shift(newFile: File) = GlobalLogBacking(newFile, Some(file), newBackingFile)
|
||||
|
||||
/** Shifts the current backing file to `last` and sets the current backing to a new temporary file generated by `newBackingFile`. */
|
||||
/**
|
||||
* Shifts the current backing file to `last` and sets the current backing to a new temporary file
|
||||
* generated by `newBackingFile`.
|
||||
*/
|
||||
def shiftNew() = shift(newBackingFile())
|
||||
|
||||
/**
|
||||
* If there is a previous backing file in `last`, that becomes the current backing file and the previous backing is cleared.
|
||||
* Otherwise, no changes are made.
|
||||
* If there is a previous backing file in `last`, that becomes the current backing file and the
|
||||
* previous backing is cleared. Otherwise, no changes are made.
|
||||
*/
|
||||
def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile)
|
||||
|
||||
|
|
@ -58,7 +61,7 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
|
|||
|
||||
object GlobalLogBacking {
|
||||
def apply(newBackingFile: => File): GlobalLogBacking =
|
||||
GlobalLogBacking(newBackingFile, None, newBackingFile _)
|
||||
GlobalLogBacking(newBackingFile, None, () => newBackingFile)
|
||||
}
|
||||
|
||||
object GlobalLogging {
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ private[sbt] object JLine3 {
|
|||
val bytes = new Array[Byte](4)
|
||||
var i = 0
|
||||
var res = -2
|
||||
do {
|
||||
while (i < 4 && res == -2) {
|
||||
inputStream.read() match {
|
||||
case -1 => res = -1
|
||||
case byte =>
|
||||
|
|
@ -94,8 +94,7 @@ private[sbt] object JLine3 {
|
|||
if (it.hasNext) res = it.next
|
||||
} catch { case _: CharacterCodingException => }
|
||||
}
|
||||
|
||||
} while (i < 4 && res == -2)
|
||||
}
|
||||
res
|
||||
}
|
||||
private[this] def wrapTerminal(term: Terminal): JTerminal = {
|
||||
|
|
@ -210,7 +209,9 @@ private[sbt] object JLine3 {
|
|||
term.getBooleanCapability(cap.toString)
|
||||
def getAttributes(): Attributes = attributesFromMap(term.getAttributes)
|
||||
def getSize(): Size = new Size(term.getWidth, term.getHeight)
|
||||
def setAttributes(a: Attributes): Unit = {} // don't allow the jline line reader to change attributes
|
||||
def setAttributes(
|
||||
a: Attributes
|
||||
): Unit = {} // don't allow the jline line reader to change attributes
|
||||
def setSize(size: Size): Unit = term.setSize(size.getColumns, size.getRows)
|
||||
|
||||
override def enterRawMode(): Attributes = {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ package sbt.internal.util
|
|||
import sbt.util._
|
||||
|
||||
/**
|
||||
* Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at `level`.
|
||||
* A line is delimited by `nl`, which is by default the platform line separator.
|
||||
* Provides a `java.io.Writer` interface to a `Logger`. Content is line-buffered and logged at
|
||||
* `level`. A line is delimited by `nl`, which is by default the platform line separator.
|
||||
*/
|
||||
class LoggerWriter(
|
||||
delegate: Logger,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import sbt.internal.util.codec.JsonProtocol._
|
|||
import sbt.util._
|
||||
import scala.reflect.runtime.universe.TypeTag
|
||||
import sjsonnew.JsonFormat
|
||||
import sbt.internal.util.appmacro.StringTypeTag
|
||||
|
||||
private[sbt] trait MiniLogger {
|
||||
def log[T](level: Level.Value, message: ObjectEvent[T]): Unit
|
||||
|
|
@ -45,7 +46,7 @@ class ManagedLogger(
|
|||
if (terminal.fold(true)(_.isSuccessEnabled)) {
|
||||
infoEvent[SuccessEvent](SuccessEvent(message))(
|
||||
implicitly[JsonFormat[SuccessEvent]],
|
||||
StringTypeTag.fast[SuccessEvent],
|
||||
StringTypeTag[SuccessEvent],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,30 +55,14 @@ class ManagedLogger(
|
|||
LogExchange.registerStringCodec[A]
|
||||
}
|
||||
|
||||
@deprecated("Use macro-powered StringTypeTag.fast instead", "1.4.0")
|
||||
final def debugEvent[A](event: => A, f: JsonFormat[A], t: TypeTag[A]): Unit =
|
||||
debugEvent(event)(f, StringTypeTag.apply(t))
|
||||
@deprecated("Use macro-powered StringTypeTag.fast instead", "1.4.0")
|
||||
final def infoEvent[A](event: => A, f: JsonFormat[A], t: TypeTag[A]): Unit =
|
||||
infoEvent(event)(f, StringTypeTag.apply(t))
|
||||
@deprecated("Use macro-powered StringTypeTag.fast instead", "1.4.0")
|
||||
final def warnEvent[A](event: => A, f: JsonFormat[A], t: TypeTag[A]): Unit =
|
||||
warnEvent(event)(f, StringTypeTag.apply(t))
|
||||
@deprecated("Use macro-powered StringTypeTag.fast instead", "1.4.0")
|
||||
final def errorEvent[A](event: => A, f: JsonFormat[A], t: TypeTag[A]): Unit =
|
||||
errorEvent(event)(f, StringTypeTag.apply(t))
|
||||
|
||||
final def debugEvent[A: JsonFormat: StringTypeTag](event: => A): Unit =
|
||||
logEvent(Level.Debug, event)
|
||||
final def infoEvent[A: JsonFormat: StringTypeTag](event: => A): Unit = logEvent(Level.Info, event)
|
||||
final def warnEvent[A: JsonFormat: StringTypeTag](event: => A): Unit = logEvent(Level.Warn, event)
|
||||
final def errorEvent[A: JsonFormat: StringTypeTag](event: => A): Unit =
|
||||
logEvent(Level.Error, event)
|
||||
@deprecated("Use macro-powered StringTypeTag.fast instead", "1.4.0")
|
||||
def logEvent[A](level: Level.Value, event: => A, f: JsonFormat[A], t: TypeTag[A]): Unit =
|
||||
logEvent(level, event)(f, StringTypeTag.apply(t))
|
||||
def logEvent[A: JsonFormat](level: Level.Value, event: => A)(
|
||||
implicit tag: StringTypeTag[A]
|
||||
def logEvent[A: JsonFormat](level: Level.Value, event: => A)(implicit
|
||||
tag: StringTypeTag[A]
|
||||
): Unit = {
|
||||
val v: A = event
|
||||
// println("logEvent " + tag.key)
|
||||
|
|
|
|||
|
|
@ -159,11 +159,10 @@ private[sbt] object ProgressState {
|
|||
private val SERVER_IS_RUNNING_LENGTH = SERVER_IS_RUNNING.length + 3
|
||||
|
||||
/**
|
||||
* Receives a new task report and replaces the old one. In the event that the new
|
||||
* report has fewer lines than the previous report, padding lines are added on top
|
||||
* so that the console log lines remain contiguous. When a console line is printed
|
||||
* at the info or greater level, we can decrement the padding because the console
|
||||
* line will have filled in the blank line.
|
||||
* Receives a new task report and replaces the old one. In the event that the new report has fewer
|
||||
* lines than the previous report, padding lines are added on top so that the console log lines
|
||||
* remain contiguous. When a console line is printed at the info or greater level, we can
|
||||
* decrement the padding because the console line will have filled in the blank line.
|
||||
*/
|
||||
private[sbt] def updateProgressState(
|
||||
pe: ProgressEvent,
|
||||
|
|
|
|||
|
|
@ -14,19 +14,17 @@ object StackTrace {
|
|||
def isSbtClass(name: String) = name.startsWith("sbt.") || name.startsWith("xsbt.")
|
||||
|
||||
/**
|
||||
* Return a printable representation of the stack trace associated
|
||||
* with t. Information about t and its Throwable causes is included.
|
||||
* The number of lines to be included for each Throwable is configured
|
||||
* via d which should be greater than or equal to 0.
|
||||
* Return a printable representation of the stack trace associated with t. Information about t and
|
||||
* its Throwable causes is included. The number of lines to be included for each Throwable is
|
||||
* configured via d which should be greater than or equal to 0.
|
||||
*
|
||||
* - If d is 0, then all elements are included up to (but not including)
|
||||
* the first element that comes from sbt.
|
||||
* - If d is greater than 0, then up to that many lines are included,
|
||||
* where the line for the Throwable is counted plus one line for each stack element.
|
||||
* Less lines will be included if there are not enough stack elements.
|
||||
* - If d is 0, then all elements are included up to (but not including) the first element that
|
||||
* comes from sbt.
|
||||
* - If d is greater than 0, then up to that many lines are included, where the line for the
|
||||
* Throwable is counted plus one line for each stack element. Less lines will be included if
|
||||
* there are not enough stack elements.
|
||||
*
|
||||
* See also ConsoleAppender where d <= 2 is treated specially by
|
||||
* printing a prepared statement.
|
||||
* See also ConsoleAppender where d <= 2 is treated specially by printing a prepared statement.
|
||||
*/
|
||||
def trimmedLines(t: Throwable, d: Int): List[String] = {
|
||||
require(d >= 0)
|
||||
|
|
@ -35,8 +33,7 @@ object StackTrace {
|
|||
def appendStackTrace(t: Throwable, first: Boolean): Unit = {
|
||||
|
||||
val include: StackTraceElement => Boolean =
|
||||
if (d == 0)
|
||||
element => !isSbtClass(element.getClassName)
|
||||
if (d == 0) element => !isSbtClass(element.getClassName)
|
||||
else {
|
||||
var count = d - 1
|
||||
(_ => { count -= 1; count >= 0 })
|
||||
|
|
@ -69,16 +66,15 @@ object StackTrace {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a printable representation of the stack trace associated
|
||||
* with t. Information about t and its Throwable causes is included.
|
||||
* The number of lines to be included for each Throwable is configured
|
||||
* via d which should be greater than or equal to 0.
|
||||
* Return a printable representation of the stack trace associated with t. Information about t and
|
||||
* its Throwable causes is included. The number of lines to be included for each Throwable is
|
||||
* configured via d which should be greater than or equal to 0.
|
||||
*
|
||||
* - If d is 0, then all elements are included up to (but not including)
|
||||
* the first element that comes from sbt.
|
||||
* - If d is greater than 0, then up to that many lines are included,
|
||||
* where the line for the Throwable is counted plus one line for each stack element.
|
||||
* Less lines will be included if there are not enough stack elements.
|
||||
* - If d is 0, then all elements are included up to (but not including) the first element that
|
||||
* comes from sbt.
|
||||
* - If d is greater than 0, then up to that many lines are included, where the line for the
|
||||
* Throwable is counted plus one line for each stack element. Less lines will be included if
|
||||
* there are not enough stack elements.
|
||||
*/
|
||||
def trimmed(t: Throwable, d: Int): String =
|
||||
trimmedLines(t, d).mkString(IO.Newline)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ trait Terminal extends AutoCloseable {
|
|||
* config which is updated if it has been more than a second since the last update. It is thus
|
||||
* possible for this value to be stale.
|
||||
*
|
||||
* @return the terminal width.
|
||||
* @return
|
||||
* the terminal width.
|
||||
*/
|
||||
def getWidth: Int
|
||||
|
||||
|
|
@ -35,78 +36,88 @@ trait Terminal extends AutoCloseable {
|
|||
* config which is updated if it has been more than a second since the last update. It is thus
|
||||
* possible for this value to be stale.
|
||||
*
|
||||
* @return the terminal height.
|
||||
* @return
|
||||
* the terminal height.
|
||||
*/
|
||||
def getHeight: Int
|
||||
|
||||
/**
|
||||
* Returns the height and width of the current line that is displayed on the terminal. If the
|
||||
* most recently flushed byte is a newline, this will be `(0, 0)`.
|
||||
* Returns the height and width of the current line that is displayed on the terminal. If the most
|
||||
* recently flushed byte is a newline, this will be `(0, 0)`.
|
||||
*
|
||||
* @return the (height, width) pair
|
||||
* @return
|
||||
* the (height, width) pair
|
||||
*/
|
||||
def getLineHeightAndWidth(line: String): (Int, Int)
|
||||
|
||||
/**
|
||||
* Gets the input stream for this Terminal. This could be a wrapper around System.in for the
|
||||
* process or it could be a remote input stream for a network channel.
|
||||
* @return the input stream.
|
||||
* @return
|
||||
* the input stream.
|
||||
*/
|
||||
def inputStream: InputStream
|
||||
|
||||
/**
|
||||
* Gets the output stream for this Terminal.
|
||||
* @return the output stream.
|
||||
* @return
|
||||
* the output stream.
|
||||
*/
|
||||
def outputStream: OutputStream
|
||||
|
||||
/**
|
||||
* Gets the error stream for this Terminal.
|
||||
* @return the error stream.
|
||||
* @return
|
||||
* the error stream.
|
||||
*/
|
||||
def errorStream: OutputStream
|
||||
|
||||
/**
|
||||
* Returns true if the terminal supports ansi characters.
|
||||
*
|
||||
* @return true if the terminal supports ansi escape codes.
|
||||
* @return
|
||||
* true if the terminal supports ansi escape codes.
|
||||
*/
|
||||
def isAnsiSupported: Boolean
|
||||
|
||||
/**
|
||||
* Returns true if color is enabled for this terminal.
|
||||
*
|
||||
* @return true if color is enabled for this terminal.
|
||||
* @return
|
||||
* true if color is enabled for this terminal.
|
||||
*/
|
||||
def isColorEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Returns true if the terminal has echo enabled.
|
||||
*
|
||||
* @return true if the terminal has echo enabled.
|
||||
* @return
|
||||
* true if the terminal has echo enabled.
|
||||
*/
|
||||
def isEchoEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Returns true if the terminal has success enabled, which it may not if it is for batch
|
||||
* commands because the client will print the success results when received from the
|
||||
* server.
|
||||
* Returns true if the terminal has success enabled, which it may not if it is for batch commands
|
||||
* because the client will print the success results when received from the server.
|
||||
*
|
||||
* @return true if the terminal has success enabled
|
||||
* @return
|
||||
* true if the terminal has success enabled
|
||||
*/
|
||||
def isSuccessEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Returns true if the terminal has supershell enabled.
|
||||
*
|
||||
* @return true if the terminal has supershell enabled.
|
||||
* @return
|
||||
* true if the terminal has supershell enabled.
|
||||
*/
|
||||
def isSupershellEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Toggles whether or not the terminal should echo characters back to stdout
|
||||
*
|
||||
* @return the previous value of the toggle
|
||||
* @return
|
||||
* the previous value of the toggle
|
||||
*/
|
||||
def setEchoEnabled(toggle: Boolean): Unit
|
||||
|
||||
|
|
@ -118,17 +129,19 @@ trait Terminal extends AutoCloseable {
|
|||
|
||||
/**
|
||||
* Returns the last line written to the terminal's output stream.
|
||||
* @return the last line
|
||||
* @return
|
||||
* the last line
|
||||
*/
|
||||
private[sbt] def getLastLine: Option[String]
|
||||
|
||||
/**
|
||||
* Returns the buffered lines that have been written to the terminal. The
|
||||
* main use case is to display the system startup log lines when a client
|
||||
* connects to a booting server. This could also be used to implement a more
|
||||
* tmux like experience where multiple clients connect to the same console.
|
||||
* Returns the buffered lines that have been written to the terminal. The main use case is to
|
||||
* display the system startup log lines when a client connects to a booting server. This could
|
||||
* also be used to implement a more tmux like experience where multiple clients connect to the
|
||||
* same console.
|
||||
*
|
||||
* @return the lines
|
||||
* @return
|
||||
* the lines
|
||||
*/
|
||||
private[sbt] def getLines: Seq[String]
|
||||
|
||||
|
|
@ -143,7 +156,8 @@ trait Terminal extends AutoCloseable {
|
|||
private[sbt] final def withRawInput[T](f: => T): T = {
|
||||
enterRawMode()
|
||||
try f
|
||||
catch { case e: InterruptedIOException => throw new InterruptedException } finally exitRawMode()
|
||||
catch { case e: InterruptedIOException => throw new InterruptedException }
|
||||
finally exitRawMode()
|
||||
}
|
||||
private[sbt] def enterRawMode(): Unit
|
||||
private[sbt] def exitRawMode(): Unit
|
||||
|
|
@ -162,8 +176,10 @@ trait Terminal extends AutoCloseable {
|
|||
* Returns the number of lines that the input string will cover given the current width of the
|
||||
* terminal.
|
||||
*
|
||||
* @param line the input line
|
||||
* @return the number of lines that the line will cover on the terminal
|
||||
* @param line
|
||||
* the input line
|
||||
* @return
|
||||
* the number of lines that the line will cover on the terminal
|
||||
*/
|
||||
private[sbt] def lineCount(line: String): Int = {
|
||||
val lines = EscHelpers.stripColorsAndMoves(line).split('\n')
|
||||
|
|
@ -249,11 +265,12 @@ object Terminal {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if System.in is attached. When sbt is run as a subprocess, like in scripted or
|
||||
* as a server, System.in will not be attached and this method will return false. Otherwise
|
||||
* it will return true.
|
||||
* Returns true if System.in is attached. When sbt is run as a subprocess, like in scripted or as
|
||||
* a server, System.in will not be attached and this method will return false. Otherwise it will
|
||||
* return true.
|
||||
*
|
||||
* @return true if System.in is attached.
|
||||
* @return
|
||||
* true if System.in is attached.
|
||||
*/
|
||||
def systemInIsAttached: Boolean = attached.get
|
||||
|
||||
|
|
@ -264,7 +281,8 @@ object Terminal {
|
|||
|
||||
/**
|
||||
* Returns an InputStream that will throw a [[ClosedChannelException]] if read returns -1.
|
||||
* @return the wrapped InputStream.
|
||||
* @return
|
||||
* the wrapped InputStream.
|
||||
*/
|
||||
private[sbt] def throwOnClosedSystemIn(in: InputStream): InputStream = new InputStream {
|
||||
override def available(): Int = in.available()
|
||||
|
|
@ -276,11 +294,12 @@ object Terminal {
|
|||
}
|
||||
|
||||
/**
|
||||
* Provides a wrapper around System.in. The wrapped stream in will check if the terminal is attached
|
||||
* in available and read. If a read returns -1, it will mark System.in as unattached so that
|
||||
* it can be detected by [[systemInIsAttached]].
|
||||
* Provides a wrapper around System.in. The wrapped stream in will check if the terminal is
|
||||
* attached in available and read. If a read returns -1, it will mark System.in as unattached so
|
||||
* that it can be detected by [[systemInIsAttached]].
|
||||
*
|
||||
* @return the wrapped InputStream
|
||||
* @return
|
||||
* the wrapped InputStream
|
||||
*/
|
||||
private[sbt] def wrappedSystemIn: InputStream = WrappedSystemIn
|
||||
|
||||
|
|
@ -303,10 +322,9 @@ object Terminal {
|
|||
|
||||
/**
|
||||
* Indicates whether formatting has been disabled in environment variables.
|
||||
* 1. -Dsbt.log.noformat=true means no formatting.
|
||||
* 2. -Dsbt.color=always/auto/never/true/false
|
||||
* 3. -Dsbt.colour=always/auto/never/true/false
|
||||
* 4. -Dsbt.log.format=always/auto/never/true/false
|
||||
* 1. -Dsbt.log.noformat=true means no formatting. 2. -Dsbt.color=always/auto/never/true/false
|
||||
* 3. -Dsbt.colour=always/auto/never/true/false 4.
|
||||
* -Dsbt.log.format=always/auto/never/true/false
|
||||
*/
|
||||
private[this] lazy val logFormatEnabled: Option[Boolean] = {
|
||||
sys.props.get("sbt.log.noformat") match {
|
||||
|
|
@ -342,11 +360,14 @@ object Terminal {
|
|||
private[sbt] def canPollSystemIn: Boolean = hasConsole && !isDumbTerminal && hasVirtualIO
|
||||
|
||||
/**
|
||||
*
|
||||
* @param isServer toggles whether or not this is a server of client process
|
||||
* @param f the thunk to run
|
||||
* @tparam T the result type of the thunk
|
||||
* @return the result of the thunk
|
||||
* @param isServer
|
||||
* toggles whether or not this is a server of client process
|
||||
* @param f
|
||||
* the thunk to run
|
||||
* @tparam T
|
||||
* the result type of the thunk
|
||||
* @return
|
||||
* the result of the thunk
|
||||
*/
|
||||
private[sbt] def withStreams[T](isServer: Boolean, isSubProcess: Boolean)(f: => T): T = {
|
||||
// In ci environments, don't touch the io streams unless run with -Dsbt.io.virtual=true
|
||||
|
|
@ -584,34 +605,33 @@ object Terminal {
|
|||
/**
|
||||
* A wrapped instance of a jline.Terminal2 instance. It should only ever be changed when the
|
||||
* backgrounds sbt with ctrl+z and then foregrounds sbt which causes a call to reset. The
|
||||
* Terminal.console method returns this terminal and the ConsoleChannel delegates its
|
||||
* terminal method to it.
|
||||
* Terminal.console method returns this terminal and the ConsoleChannel delegates its terminal
|
||||
* method to it.
|
||||
*/
|
||||
private[this] val consoleTerminalHolder: AtomicReference[Terminal] =
|
||||
new AtomicReference(SimpleTerminal)
|
||||
|
||||
/**
|
||||
* The terminal that is currently being used by the proxyInputStream and proxyOutputStream.
|
||||
* It is set through the Terminal.set method which is called by the SetTerminal command, which
|
||||
* is used to change the terminal during task evaluation. This allows us to route System.in and
|
||||
* System.out through the terminal's input and output streams.
|
||||
* The terminal that is currently being used by the proxyInputStream and proxyOutputStream. It is
|
||||
* set through the Terminal.set method which is called by the SetTerminal command, which is used
|
||||
* to change the terminal during task evaluation. This allows us to route System.in and System.out
|
||||
* through the terminal's input and output streams.
|
||||
*/
|
||||
private[this] val activeTerminal = new AtomicReference[Terminal](consoleTerminalHolder.get)
|
||||
|
||||
/**
|
||||
* The boot input stream allows a remote client to forward input to the sbt process while
|
||||
* it is still loading. It works by updating proxyInputStream to read from the
|
||||
* value of bootInputStreamHolder if it is non-null as well as from the normal process
|
||||
* console io (assuming there is console io).
|
||||
* The boot input stream allows a remote client to forward input to the sbt process while it is
|
||||
* still loading. It works by updating proxyInputStream to read from the value of
|
||||
* bootInputStreamHolder if it is non-null as well as from the normal process console io (assuming
|
||||
* there is console io).
|
||||
*/
|
||||
private[this] val bootInputStreamHolder = new AtomicReference[InputStream]
|
||||
|
||||
/**
|
||||
* The boot output stream allows sbt to relay the bytes written to stdout to one or
|
||||
* more remote clients while the sbt build is loading and hasn't yet loaded a server.
|
||||
* The output stream of TerminalConsole is updated to write to value of
|
||||
* bootOutputStreamHolder when it is non-null as well as the normal process console
|
||||
* output stream.
|
||||
* The boot output stream allows sbt to relay the bytes written to stdout to one or more remote
|
||||
* clients while the sbt build is loading and hasn't yet loaded a server. The output stream of
|
||||
* TerminalConsole is updated to write to value of bootOutputStreamHolder when it is non-null as
|
||||
* well as the normal process console output stream.
|
||||
*/
|
||||
private[this] val bootOutputStreamHolder = new AtomicReference[OutputStream]
|
||||
private[sbt] def setBootStreams(
|
||||
|
|
@ -912,7 +932,7 @@ object Terminal {
|
|||
val out: OutputStream,
|
||||
override val errorStream: OutputStream,
|
||||
override private[sbt] val name: String
|
||||
) extends Terminal {
|
||||
) extends Terminal { self =>
|
||||
private[sbt] def getSizeImpl: (Int, Int)
|
||||
private[this] val sizeRefreshPeriod = 1.second
|
||||
private[this] val size =
|
||||
|
|
@ -951,7 +971,7 @@ object Terminal {
|
|||
|
||||
override val outputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = throwIfClosed {
|
||||
write(Array((b & 0xFF).toByte))
|
||||
write(Array((b & 0xff).toByte))
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = throwIfClosed {
|
||||
withWriteLock(doWrite(b))
|
||||
|
|
@ -964,8 +984,12 @@ object Terminal {
|
|||
private def doWrite(rawBytes: Array[Byte]): Unit = withPrintStream { ps =>
|
||||
val (toWrite, len) =
|
||||
if (rawBytes.contains(27.toByte)) {
|
||||
if (!isAnsiSupported || !isColorEnabled)
|
||||
EscHelpers.strip(rawBytes, stripAnsi = !isAnsiSupported, stripColor = !isColorEnabled)
|
||||
if (!Terminal.isAnsiSupported || !Terminal.isColorEnabled)
|
||||
EscHelpers.strip(
|
||||
rawBytes,
|
||||
stripAnsi = !Terminal.isAnsiSupported,
|
||||
stripColor = !Terminal.isColorEnabled
|
||||
)
|
||||
else (rawBytes, rawBytes.length)
|
||||
} else (rawBytes, rawBytes.length)
|
||||
val bytes = if (len < toWrite.length) toWrite.take(len) else toWrite
|
||||
|
|
@ -999,7 +1023,7 @@ object Terminal {
|
|||
}
|
||||
}
|
||||
private lazy val nullInputStream: InputStream = () => {
|
||||
try this.synchronized(this.wait)
|
||||
try this.synchronized(this.wait())
|
||||
catch { case _: InterruptedException => }
|
||||
-1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,10 @@ private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: In
|
|||
val isShift = (controlKeyState & SHIFT_PRESSED) > 0;
|
||||
if (keyEvent.keyDown) {
|
||||
if (keyEvent.uchar > 0) {
|
||||
if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
|
||||
&& isAlt && !isCtrl) {
|
||||
if (
|
||||
((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
|
||||
&& isAlt && !isCtrl
|
||||
) {
|
||||
sb.append('\u001B') // ESC
|
||||
}
|
||||
if (isShift && keyEvent.keyCode == 9) {
|
||||
|
|
@ -108,15 +110,15 @@ private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: In
|
|||
case 0x77 /* VK_F8 */ => getCapability(Capability.key_f8)
|
||||
case 0x78 /* VK_F9 */ => getCapability(Capability.key_f9)
|
||||
case 0x79 /* VK_F10 */ => getCapability(Capability.key_f10)
|
||||
case 0x7A /* VK_F11 */ => getCapability(Capability.key_f11)
|
||||
case 0x7B /* VK_F12 */ => getCapability(Capability.key_f12)
|
||||
case 0x7a /* VK_F11 */ => getCapability(Capability.key_f11)
|
||||
case 0x7b /* VK_F12 */ => getCapability(Capability.key_f12)
|
||||
// VK_END, VK_INSERT and VK_DELETE are not in the ansi key bindings so we
|
||||
// have to manually apply the the sequences here and in JLine3.wrap
|
||||
case 0x23 /* VK_END */ =>
|
||||
Option(getCapability(Capability.key_end)).getOrElse("\u001B[4~")
|
||||
case 0x2D /* VK_INSERT */ =>
|
||||
case 0x2d /* VK_INSERT */ =>
|
||||
Option(getCapability(Capability.key_ic)).getOrElse("\u001B[2~")
|
||||
case 0x2E /* VK_DELETE */ =>
|
||||
case 0x2e /* VK_DELETE */ =>
|
||||
Option(getCapability(Capability.key_dc)).getOrElse("\u001B[3~")
|
||||
case _ => null
|
||||
}
|
||||
|
|
@ -142,7 +144,7 @@ private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: In
|
|||
override def read(): Int = {
|
||||
buffer.poll match {
|
||||
case null =>
|
||||
readConsoleInput().foreach(b => buffer.put(b & 0xFF))
|
||||
readConsoleInput().foreach(b => buffer.put(b & 0xff))
|
||||
if (!Thread.interrupted) read() else throw new InterruptedException
|
||||
case b => b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
package sbt.util
|
||||
|
||||
/**
|
||||
* An enumeration defining the levels available for logging. A level includes all of the levels
|
||||
* with id larger than its own id. For example, Warn (id=3) includes Error (id=4).
|
||||
* An enumeration defining the levels available for logging. A level includes all of the levels with
|
||||
* id larger than its own id. For example, Warn (id=3) includes Error (id=4).
|
||||
*/
|
||||
object Level extends Enumeration {
|
||||
val Debug = Value(1, "debug")
|
||||
|
|
@ -18,15 +18,18 @@ object Level extends Enumeration {
|
|||
val Error = Value(4, "error")
|
||||
|
||||
/**
|
||||
* Defines the label to use for success messages.
|
||||
* Because the label for levels is defined in this module, the success label is also defined here.
|
||||
* Defines the label to use for success messages. Because the label for levels is defined in this
|
||||
* module, the success label is also defined here.
|
||||
*/
|
||||
val SuccessLabel = "success"
|
||||
|
||||
def union(a: Value, b: Value) = if (a.id < b.id) a else b
|
||||
def unionAll(vs: Seq[Value]) = vs reduceLeft union
|
||||
|
||||
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */
|
||||
/**
|
||||
* Returns the level with the given name wrapped in Some, or None if no level exists for that
|
||||
* name.
|
||||
*/
|
||||
def apply(s: String) = values.find(s == _.toString)
|
||||
|
||||
/** Same as apply, defined for use in pattern matching. */
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ import org.apache.logging.log4j.core.config.LoggerConfig
|
|||
import org.apache.logging.log4j.core.layout.PatternLayout
|
||||
import org.apache.logging.log4j.core.{ LoggerContext => XLoggerContext }
|
||||
import org.apache.logging.log4j.{ LogManager => XLogManager }
|
||||
import sbt.internal.util._
|
||||
import sbt.internal.util.{ Appender, ManagedLogger, TraceEvent, SuccessEvent, Util }
|
||||
import sbt.internal.util.appmacro.StringTypeTag
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import scala.collection.concurrent
|
||||
|
|
@ -34,6 +35,7 @@ sealed abstract class LogExchange {
|
|||
def unbindLoggerAppenders(loggerName: String): Unit = {
|
||||
LoggerContext.globalContext.clearAppenders(loggerName)
|
||||
}
|
||||
|
||||
def bindLoggerAppenders(
|
||||
loggerName: String,
|
||||
appenders: Seq[(Appender, Level.Value)]
|
||||
|
|
@ -45,9 +47,11 @@ sealed abstract class LogExchange {
|
|||
// Construct these StringTypeTags manually, because they're used at the very startup of sbt
|
||||
// and we'll try not to initialize the universe by using the StringTypeTag.apply that requires a TypeTag
|
||||
// A better long-term solution could be to make StringTypeTag.apply a macro.
|
||||
lazy val stringTypeTagThrowable = StringTypeTag[Throwable]("scala.Throwable")
|
||||
lazy val stringTypeTagTraceEvent = StringTypeTag[TraceEvent]("sbt.internal.util.TraceEvent")
|
||||
lazy val stringTypeTagSuccessEvent = StringTypeTag[SuccessEvent]("sbt.internal.util.SuccessEvent")
|
||||
lazy val stringTypeTagThrowable = StringTypeTag.manually[Throwable]("java.lang.Throwable")
|
||||
lazy val stringTypeTagTraceEvent =
|
||||
StringTypeTag.manually[TraceEvent]("sbt.internal.util.TraceEvent")
|
||||
lazy val stringTypeTagSuccessEvent =
|
||||
StringTypeTag.manually[SuccessEvent]("sbt.internal.util.SuccessEvent")
|
||||
|
||||
private[sbt] def initStringCodecs(): Unit = {
|
||||
import sbt.internal.util.codec.SuccessEventShowLines._
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import java.util.Optional
|
|||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* This is intended to be the simplest logging interface for use by code that wants to log.
|
||||
* It does not include configuring the logger.
|
||||
* This is intended to be the simplest logging interface for use by code that wants to log. It does
|
||||
* not include configuring the logger.
|
||||
*/
|
||||
abstract class Logger extends xLogger {
|
||||
final def verbose(message: => String): Unit = debug(message)
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||
import scala.collection.JavaConverters._
|
||||
|
||||
/**
|
||||
* Provides a context for generating loggers during task evaluation. The logger context
|
||||
* can be initialized for a single command evaluation run and all of the resources
|
||||
* created (such as cached logger appenders) can be cleaned up after task evaluation.
|
||||
* This trait evolved out of LogExchange when it became clear that it was very difficult
|
||||
* to manage the loggers and appenders without introducing memory leaks.
|
||||
* Provides a context for generating loggers during task evaluation. The logger context can be
|
||||
* initialized for a single command evaluation run and all of the resources created (such as cached
|
||||
* logger appenders) can be cleaned up after task evaluation. This trait evolved out of LogExchange
|
||||
* when it became clear that it was very difficult to manage the loggers and appenders without
|
||||
* introducing memory leaks.
|
||||
*/
|
||||
sealed trait LoggerContext extends AutoCloseable {
|
||||
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger
|
||||
|
|
@ -45,9 +45,8 @@ object LoggerContext {
|
|||
}
|
||||
}
|
||||
def log[T](level: Level.Value, message: ObjectEvent[T]): Unit = {
|
||||
consoleAppenders.forEach {
|
||||
case (a, l) =>
|
||||
if (level.compare(l) >= 0) a.appendObjectEvent(level, message)
|
||||
consoleAppenders.forEach { case (a, l) =>
|
||||
if (level.compare(l) >= 0) a.appendObjectEvent(level, message)
|
||||
}
|
||||
}
|
||||
def addAppender(newAppender: (Appender, Level.Value)): Unit =
|
||||
|
|
|
|||
|
|
@ -8,29 +8,30 @@
|
|||
package sbt.util
|
||||
|
||||
import sbt.internal.util._
|
||||
|
||||
import sbt.internal.util.appmacro.StringTypeTag
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class LogExchangeSpec extends AnyFlatSpec with Matchers {
|
||||
import LogExchange._
|
||||
|
||||
checkTypeTag("stringTypeTagThrowable", stringTypeTagThrowable, StringTypeTag.fast[Throwable])
|
||||
checkTypeTag("stringTypeTagThrowable", stringTypeTagThrowable, StringTypeTag[Throwable])
|
||||
|
||||
checkTypeTag(
|
||||
"stringTypeTagTraceEvent",
|
||||
stringTypeTagTraceEvent,
|
||||
StringTypeTag.fast[TraceEvent]
|
||||
StringTypeTag[TraceEvent]
|
||||
)
|
||||
checkTypeTag(
|
||||
"stringTypeTagSuccessEvent",
|
||||
stringTypeTagSuccessEvent,
|
||||
StringTypeTag.fast[SuccessEvent]
|
||||
StringTypeTag[SuccessEvent]
|
||||
)
|
||||
|
||||
private def checkTypeTag[A](name: String, inc: StringTypeTag[A], exp: StringTypeTag[A]): Unit =
|
||||
s"LogExchange.$name" should s"match real StringTypeTag[$exp]" in {
|
||||
val StringTypeTag(incomingString) = inc
|
||||
val StringTypeTag(expectedString) = exp
|
||||
val incomingString = inc.key
|
||||
val expectedString = exp.key
|
||||
if ((incomingString startsWith "scala.") || (expectedString startsWith "scala.")) {
|
||||
// > historically [Scala] has been inconsistent whether `scala.` is included, or not
|
||||
// > would it be hard to make the test accept either result?
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
final val MaxSegments = 10
|
||||
|
||||
/* Tests that content written through a LoggerWriter is properly passed to the underlying Logger.
|
||||
* Each line, determined by the specified newline separator, must be logged at the correct logging level. */
|
||||
* Each line, determined by the specified newline separator, must be logged at the correct logging level. */
|
||||
property("properly logged") = forAll { (output: Output, newLine: NewLine) =>
|
||||
import output.{ lines, level }
|
||||
val log = new RecordingLogger
|
||||
|
|
@ -32,8 +32,8 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
}
|
||||
|
||||
/**
|
||||
* Displays a LogEvent in a useful format for debugging. In particular, we are only interested in `Log` types
|
||||
* and non-printable characters should be escaped
|
||||
* Displays a LogEvent in a useful format for debugging. In particular, we are only interested in
|
||||
* `Log` types and non-printable characters should be escaped
|
||||
*/
|
||||
def show(event: LogEvent): String =
|
||||
event match {
|
||||
|
|
@ -42,9 +42,9 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
}
|
||||
|
||||
/**
|
||||
* Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
|
||||
* represented as separately written segments (ToLog instances). ToLog.`byCharacter`
|
||||
* indicates whether to write the segment by character (true) or all at once (false)
|
||||
* Writes the given lines to the Writer. `lines` is taken to be a list of lines, which are
|
||||
* represented as separately written segments (ToLog instances). ToLog.`byCharacter` indicates
|
||||
* whether to write the segment by character (true) or all at once (false)
|
||||
*/
|
||||
def logLines(writer: Writer, lines: List[List[ToLog]], newLine: String): Unit = {
|
||||
for (line <- lines; section <- line) {
|
||||
|
|
@ -58,11 +58,13 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
writer.flush()
|
||||
}
|
||||
|
||||
/** Converts the given lines in segments to lines as Strings for checking the results of the test.*/
|
||||
/**
|
||||
* Converts the given lines in segments to lines as Strings for checking the results of the test.
|
||||
*/
|
||||
def toLines(lines: List[List[ToLog]]): List[String] =
|
||||
lines.map(_.map(_.contentOnly).mkString)
|
||||
|
||||
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`.*/
|
||||
/** Checks that the expected `lines` were recorded as `events` at level `Lvl`. */
|
||||
def check(lines: List[String], events: List[LogEvent], Lvl: Level.Value): Boolean =
|
||||
(lines zip events) forall {
|
||||
case (line, log: Log) => log.level == Lvl && line == log.msg
|
||||
|
|
@ -102,7 +104,10 @@ object LogWriterTest extends Properties("Log Writer") {
|
|||
|
||||
def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
|
||||
def addNewline(l: ToLog): ToLog =
|
||||
new ToLog(l.content + "\n", l.byCharacter) // \n will be replaced by a random line terminator for all lines
|
||||
new ToLog(
|
||||
l.content + "\n",
|
||||
l.byCharacter
|
||||
) // \n will be replaced by a random line terminator for all lines
|
||||
|
||||
def listOf[T](max: Int)(implicit content: Arbitrary[T]): Gen[List[T]] =
|
||||
Gen.choose(0, max) flatMap (sz => listOfN(sz, content.arbitrary))
|
||||
|
|
@ -126,10 +131,10 @@ final class ToLog(val content: String, val byCharacter: Boolean) {
|
|||
if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
|
||||
}
|
||||
|
||||
/** Defines some utility methods for escaping unprintable characters.*/
|
||||
/** Defines some utility methods for escaping unprintable characters. */
|
||||
object Escape {
|
||||
|
||||
/** Escapes characters with code less than 20 by printing them as unicode escapes.*/
|
||||
/** Escapes characters with code less than 20 by printing them as unicode escapes. */
|
||||
def apply(s: String): String = {
|
||||
val builder = new StringBuilder(s.length)
|
||||
for (c <- s) {
|
||||
|
|
@ -145,13 +150,13 @@ object Escape {
|
|||
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
|
||||
}
|
||||
|
||||
/** Replaces a \n character at the end of a string `s` with `nl`.*/
|
||||
/** Replaces a \n character at the end of a string `s` with `nl`. */
|
||||
def newline(s: String, nl: String): String =
|
||||
if (s.endsWith("\n")) s.substring(0, s.length - 1) + nl else s
|
||||
|
||||
}
|
||||
|
||||
/** Records logging events for later retrieval.*/
|
||||
/** Records logging events for later retrieval. */
|
||||
final class RecordingLogger extends BasicLogger {
|
||||
private var events: List[LogEvent] = Nil
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package sbt.internal.util
|
|||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import sbt.util._
|
||||
import sbt.internal.util.appmacro.StringTypeTag
|
||||
import java.io.{ File, PrintWriter }
|
||||
import sbt.io.Using
|
||||
import scala.annotation.nowarn
|
||||
|
|
@ -17,8 +18,8 @@ import scala.annotation.nowarn
|
|||
class ManagedLoggerSpec extends AnyFlatSpec with Matchers {
|
||||
val context = LoggerContext()
|
||||
@nowarn
|
||||
//TODO create a new appender for testing purposes - 3/12/21
|
||||
val asyncStdout = ConsoleAppender("asyncStdout")
|
||||
// TODO create a new appender for testing purposes - 3/12/21
|
||||
val asyncStdout = ConsoleAppender()
|
||||
def newLogger(name: String): ManagedLogger = context.logger(name, None, None)
|
||||
"ManagedLogger" should "log to console" in {
|
||||
val log = newLogger("foo")
|
||||
|
|
@ -91,7 +92,7 @@ class ManagedLoggerSpec extends AnyFlatSpec with Matchers {
|
|||
} {
|
||||
pool.submit(new Runnable {
|
||||
def run(): Unit = {
|
||||
val stringTypeTag = StringTypeTag.fast[List[Int]]
|
||||
val stringTypeTag = StringTypeTag[List[Int]]
|
||||
val log = newLogger(s"foo$i")
|
||||
context.addAppender(s"foo$i", asyncStdout -> Level.Info)
|
||||
if (i % 100 == 0) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class UTF8DecoderSpec extends AnyFlatSpec {
|
|||
"emoji" should "be handled" in {
|
||||
val bytes = new LinkedBlockingQueue[Int]
|
||||
// laughing and crying emoji in utf8
|
||||
Seq(0xF0, 0x9F, 0x98, 0x82).foreach(b => bytes.put(b))
|
||||
Seq(0xf0, 0x9f, 0x98, 0x82).foreach(b => bytes.put(b))
|
||||
val inputStream = new InputStream {
|
||||
override def read(): Int = Option(bytes.poll).getOrElse(-1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,7 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
|
|||
}
|
||||
def execute(command: List[String]): Unit = execute0(command.head, command.tail)
|
||||
def execute0(command: String, args: List[String]): Unit = {
|
||||
if (command.trim.isEmpty)
|
||||
scriptError("Command was empty.")
|
||||
if (command.trim.isEmpty) scriptError("Command was empty.")
|
||||
else {
|
||||
val exitValue = sys.process.Process(command :: args, baseDirectory).!
|
||||
if (exitValue != 0)
|
||||
|
|
|
|||
|
|
@ -217,8 +217,7 @@ final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log:
|
|||
} else {
|
||||
val (included, skipped) =
|
||||
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
|
||||
if (included.isEmpty)
|
||||
log.warn("Test group " + groupName + " skipped.")
|
||||
if (included.isEmpty) log.warn("Test group " + groupName + " skipped.")
|
||||
else if (skipped.nonEmpty) {
|
||||
log.warn("Tests skipped in group " + group.getName + ":")
|
||||
skipped.foreach(testName => log.warn(" " + testName.getName))
|
||||
|
|
|
|||
|
|
@ -82,15 +82,18 @@ object Dependencies {
|
|||
|
||||
lazy val sjsonNewVersion = "0.9.1"
|
||||
def sjsonNew(n: String) = Def.setting(
|
||||
if (scalaBinaryVersion.value == "3") "com.eed3si9n" % (n + "_2.13") % sjsonNewVersion
|
||||
else "com.eed3si9n" %% n % "0.9.1"
|
||||
if (scalaBinaryVersion.value == "3")
|
||||
("com.eed3si9n" % n % sjsonNewVersion).cross(CrossVersion.for3Use2_13)
|
||||
else "com.eed3si9n" %% n % sjsonNewVersion
|
||||
) // contrabandSjsonNewVersion.value
|
||||
val sjsonNewScalaJson = sjsonNew("sjson-new-scalajson")
|
||||
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
||||
val sjsonNewCore = sjsonNew("sjson-new-core")
|
||||
|
||||
// JLine 3 version must be coordinated together with JAnsi version
|
||||
// and the JLine 2 fork version, which uses the same JAnsi
|
||||
val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-a1b0ffbb8f64bb820f4f84a0c07a0c0964507493"
|
||||
val jline =
|
||||
"org.scala-sbt.jline" % "jline" % "2.14.7-sbt-a1b0ffbb8f64bb820f4f84a0c07a0c0964507493"
|
||||
val jline3Version = "3.19.0"
|
||||
val jline3Terminal = "org.jline" % "jline-terminal" % jline3Version
|
||||
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ package bsp
|
|||
|
||||
object SourceItemKind {
|
||||
|
||||
/** The source item references a normal file. */
|
||||
/** The source item references a normal file. */
|
||||
val File: Int = 1
|
||||
|
||||
/** The source item references a directory. */
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ trait JsonRpcNotificationMessageFormats {
|
|||
unbuilder.beginObject(js)
|
||||
val jsonrpc = unbuilder.readField[String]("jsonrpc")
|
||||
val method = unbuilder.readField[String]("method")
|
||||
val params = unbuilder.lookupField("params") map {
|
||||
case x: JValue => x
|
||||
val params = unbuilder.lookupField("params") map { case x: JValue =>
|
||||
x
|
||||
}
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.JsonRpcNotificationMessage(jsonrpc, method, params)
|
||||
|
|
|
|||
|
|
@ -23,17 +23,18 @@ trait JsonRpcRequestMessageFormats {
|
|||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val jsonrpc = unbuilder.readField[String]("jsonrpc")
|
||||
val id = try {
|
||||
unbuilder.readField[String]("id")
|
||||
} catch {
|
||||
case _: DeserializationException => {
|
||||
val prefix = "\u2668" // Append prefix to show the original type was Number
|
||||
prefix + unbuilder.readField[Long]("id").toString
|
||||
val id =
|
||||
try {
|
||||
unbuilder.readField[String]("id")
|
||||
} catch {
|
||||
case _: DeserializationException => {
|
||||
val prefix = "\u2668" // Append prefix to show the original type was Number
|
||||
prefix + unbuilder.readField[Long]("id").toString
|
||||
}
|
||||
}
|
||||
}
|
||||
val method = unbuilder.readField[String]("method")
|
||||
val params = unbuilder.lookupField("params") map {
|
||||
case x: JValue => x
|
||||
val params = unbuilder.lookupField("params") map { case x: JValue =>
|
||||
x
|
||||
}
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.JsonRpcRequestMessage(jsonrpc, id, method, params)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ trait JsonRpcResponseErrorFormats {
|
|||
unbuilder.beginObject(js)
|
||||
val code = unbuilder.readField[Long]("code")
|
||||
val message = unbuilder.readField[String]("message")
|
||||
val data = unbuilder.lookupField("data") map {
|
||||
case x: JValue => x
|
||||
val data = unbuilder.lookupField("data") map { case x: JValue =>
|
||||
x
|
||||
}
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.JsonRpcResponseError(code, message, data)
|
||||
|
|
|
|||
|
|
@ -31,15 +31,16 @@ trait JsonRpcResponseMessageFormats {
|
|||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val jsonrpc = unbuilder.readField[String]("jsonrpc")
|
||||
val id = try {
|
||||
unbuilder.readField[String]("id")
|
||||
} catch {
|
||||
case _: DeserializationException =>
|
||||
unbuilder.readField[Long]("id").toString
|
||||
}
|
||||
val id =
|
||||
try {
|
||||
unbuilder.readField[String]("id")
|
||||
} catch {
|
||||
case _: DeserializationException =>
|
||||
unbuilder.readField[Long]("id").toString
|
||||
}
|
||||
|
||||
val result = unbuilder.lookupField("result") map {
|
||||
case x: JValue => x
|
||||
val result = unbuilder.lookupField("result") map { case x: JValue =>
|
||||
x
|
||||
}
|
||||
|
||||
val error =
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ object Serialization {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return A command or an invalid input description
|
||||
* @return
|
||||
* A command or an invalid input description
|
||||
*/
|
||||
@deprecated("unused", since = "1.4.0")
|
||||
def deserializeCommand(bytes: Seq[Byte]): Either[String, CommandMessage] = {
|
||||
|
|
@ -153,7 +154,8 @@ object Serialization {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return A command or an invalid input description
|
||||
* @return
|
||||
* A command or an invalid input description
|
||||
*/
|
||||
@deprecated("unused", since = "1.4.0")
|
||||
def deserializeEvent(bytes: Seq[Byte]): Either[String, Any] = {
|
||||
|
|
@ -190,7 +192,8 @@ object Serialization {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return A command or an invalid input description
|
||||
* @return
|
||||
* A command or an invalid input description
|
||||
*/
|
||||
@deprecated("unused", since = "1.4.0")
|
||||
def deserializeEventMessage(bytes: Seq[Byte]): Either[String, EventMessage] = {
|
||||
|
|
|
|||
|
|
@ -14,42 +14,42 @@ import ConcurrentRestrictions.{ Tag, TagMap, tagsKey }
|
|||
// Action, Task, and Info are intentionally invariant in their type parameter.
|
||||
// Various natural transformations used, such as PMap, require invariant type constructors for correctness
|
||||
|
||||
/** Defines a task computation*/
|
||||
/** Defines a task computation */
|
||||
sealed trait Action[T] {
|
||||
// TODO: remove after deprecated InputTask constructors are removed
|
||||
private[sbt] def mapTask(f: Task ~> Task): Action[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* A direct computation of a value.
|
||||
* If `inline` is true, `f` will be evaluated on the scheduler thread without the overhead of normal scheduling when possible.
|
||||
* This is intended as an optimization for already evaluated values or very short pure computations.
|
||||
* A direct computation of a value. If `inline` is true, `f` will be evaluated on the scheduler
|
||||
* thread without the overhead of normal scheduling when possible. This is intended as an
|
||||
* optimization for already evaluated values or very short pure computations.
|
||||
*/
|
||||
final case class Pure[T](f: () => T, `inline`: Boolean) extends Action[T] {
|
||||
private[sbt] def mapTask(f: Task ~> Task) = this
|
||||
}
|
||||
|
||||
/** Applies a function to the result of evaluating a heterogeneous list of other tasks.*/
|
||||
/** Applies a function to the result of evaluating a heterogeneous list of other tasks. */
|
||||
final case class Mapped[T, K[L[x]]](in: K[Task], f: K[Result] => T, alist: AList[K])
|
||||
extends Action[T] {
|
||||
private[sbt] def mapTask(g: Task ~> Task) = Mapped[T, K](alist.transform(in, g), f, alist)
|
||||
}
|
||||
|
||||
/** Computes another task to evaluate based on results from evaluating other tasks.*/
|
||||
/** Computes another task to evaluate based on results from evaluating other tasks. */
|
||||
final case class FlatMapped[T, K[L[x]]](in: K[Task], f: K[Result] => Task[T], alist: AList[K])
|
||||
extends Action[T] {
|
||||
private[sbt] def mapTask(g: Task ~> Task) =
|
||||
FlatMapped[T, K](alist.transform(in, g), g.fn[T] compose f, alist)
|
||||
}
|
||||
|
||||
/** A computation `in` that requires other tasks `deps` to be evaluated first.*/
|
||||
/** A computation `in` that requires other tasks `deps` to be evaluated first. */
|
||||
final case class DependsOn[T](in: Task[T], deps: Seq[Task[_]]) extends Action[T] {
|
||||
private[sbt] def mapTask(g: Task ~> Task) = DependsOn[T](g(in), deps.map(t => g(t)))
|
||||
}
|
||||
|
||||
/**
|
||||
* A computation that operates on the results of a homogeneous list of other tasks.
|
||||
* It can either return another task to be evaluated or the final value.
|
||||
* A computation that operates on the results of a homogeneous list of other tasks. It can either
|
||||
* return another task to be evaluated or the final value.
|
||||
*/
|
||||
final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T], T])
|
||||
extends Action[T] {
|
||||
|
|
@ -58,8 +58,8 @@ final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T
|
|||
}
|
||||
|
||||
/**
|
||||
* A computation that conditionally falls back to a second transformation.
|
||||
* This can be used to encode `if` conditions.
|
||||
* A computation that conditionally falls back to a second transformation. This can be used to
|
||||
* encode `if` conditions.
|
||||
*/
|
||||
final case class Selected[A, B](fab: Task[Either[A, B]], fin: Task[A => B]) extends Action[B] {
|
||||
private def ml = AList.single[Either[A, B]]
|
||||
|
|
@ -76,9 +76,12 @@ final case class Selected[A, B](fab: Task[Either[A, B]], fin: Task[A => B]) exte
|
|||
case Right(b) => std.TaskExtra.task(b)
|
||||
case Left(a) => std.TaskExtra.singleInputTask(fin).map(_(a))
|
||||
}
|
||||
FlatMapped[B, K](fab, {
|
||||
f compose std.TaskExtra.successM
|
||||
}, ml)
|
||||
FlatMapped[B, K](
|
||||
fab, {
|
||||
f compose std.TaskExtra.successM
|
||||
},
|
||||
ml
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,9 +102,13 @@ final case class Task[T](info: Info[T], work: Action[T]) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Used to provide information about a task, such as the name, description, and tags for controlling concurrent execution.
|
||||
* @param attributes Arbitrary user-defined key/value pairs describing this task
|
||||
* @param post a transformation that takes the result of evaluating this task and produces user-defined key/value pairs.
|
||||
* Used to provide information about a task, such as the name, description, and tags for controlling
|
||||
* concurrent execution.
|
||||
* @param attributes
|
||||
* Arbitrary user-defined key/value pairs describing this task
|
||||
* @param post
|
||||
* a transformation that takes the result of evaluating this task and produces user-defined
|
||||
* key/value pairs.
|
||||
*/
|
||||
final case class Info[T](
|
||||
attributes: AttributeMap = AttributeMap.empty,
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import sbt.util._
|
|||
|
||||
// no longer specific to Tasks, so 'TaskStreams' should be renamed
|
||||
/**
|
||||
* Represents a set of streams associated with a context.
|
||||
* In sbt, this is a named set of streams for a particular scoped key.
|
||||
* For example, logging for test:compile is by default sent to the "out" stream in the test:compile context.
|
||||
* Represents a set of streams associated with a context. In sbt, this is a named set of streams for
|
||||
* a particular scoped key. For example, logging for test:compile is by default sent to the "out"
|
||||
* stream in the test:compile context.
|
||||
*/
|
||||
sealed trait TaskStreams[Key] {
|
||||
|
||||
|
|
@ -36,16 +36,16 @@ sealed trait TaskStreams[Key] {
|
|||
def getOutput(sid: String = default): Output
|
||||
|
||||
/**
|
||||
* Provides a reader to read text from the stream `sid` for `key`.
|
||||
* It is the caller's responsibility to coordinate writing to the stream.
|
||||
* That is, no synchronization or ordering is provided and so this method should only be called when writing is complete.
|
||||
* Provides a reader to read text from the stream `sid` for `key`. It is the caller's
|
||||
* responsibility to coordinate writing to the stream. That is, no synchronization or ordering is
|
||||
* provided and so this method should only be called when writing is complete.
|
||||
*/
|
||||
def readText(key: Key, sid: String = default): BufferedReader
|
||||
|
||||
/**
|
||||
* Provides an output stream to read from the stream `sid` for `key`.
|
||||
* It is the caller's responsibility to coordinate writing to the stream.
|
||||
* That is, no synchronization or ordering is provided and so this method should only be called when writing is complete.
|
||||
* Provides an output stream to read from the stream `sid` for `key`. It is the caller's
|
||||
* responsibility to coordinate writing to the stream. That is, no synchronization or ordering is
|
||||
* provided and so this method should only be called when writing is complete.
|
||||
*/
|
||||
def readBinary(a: Key, sid: String = default): BufferedInputStream
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ sealed trait TaskStreams[Key] {
|
|||
/** Provides an output stream for writing to the stream with the given ID. */
|
||||
def binary(sid: String = default): BufferedOutputStream
|
||||
|
||||
/** A cache directory that is unique to the context of this streams instance.*/
|
||||
/** A cache directory that is unique to the context of this streams instance. */
|
||||
def cacheDirectory: File
|
||||
|
||||
def cacheStoreFactory: CacheStoreFactory
|
||||
|
|
@ -70,7 +70,7 @@ sealed trait TaskStreams[Key] {
|
|||
/** Obtains the default logger. */
|
||||
final lazy val log: ManagedLogger = log(default)
|
||||
|
||||
/** Creates a Logger that logs to stream with ID `sid`.*/
|
||||
/** Creates a Logger that logs to stream with ID `sid`. */
|
||||
def log(sid: String): ManagedLogger
|
||||
|
||||
private[this] def getID(s: Option[String]) = s getOrElse default
|
||||
|
|
@ -165,23 +165,22 @@ object Streams {
|
|||
make(a, sid)(f => new FileOutput(f))
|
||||
|
||||
def readText(a: Key, sid: String = default): BufferedReader =
|
||||
make(a, sid)(
|
||||
f => new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset))
|
||||
make(a, sid)(f =>
|
||||
new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset))
|
||||
)
|
||||
|
||||
def readBinary(a: Key, sid: String = default): BufferedInputStream =
|
||||
make(a, sid)(f => new BufferedInputStream(new FileInputStream(f)))
|
||||
|
||||
def text(sid: String = default): PrintWriter =
|
||||
make(a, sid)(
|
||||
f =>
|
||||
new PrintWriter(
|
||||
new DeferredWriter(
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(f), IO.defaultCharset)
|
||||
)
|
||||
make(a, sid)(f =>
|
||||
new PrintWriter(
|
||||
new DeferredWriter(
|
||||
new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(f), IO.defaultCharset)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def binary(sid: String = default): BufferedOutputStream =
|
||||
|
|
@ -206,9 +205,11 @@ object Streams {
|
|||
case null => newLock
|
||||
case l => l
|
||||
}
|
||||
try lock.synchronized {
|
||||
if (!file.exists) IO.touch(file, setModified = false)
|
||||
} finally {
|
||||
try
|
||||
lock.synchronized {
|
||||
if (!file.exists) IO.touch(file, setModified = false)
|
||||
}
|
||||
finally {
|
||||
streamLocks.remove(parent)
|
||||
()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,11 +170,10 @@ trait TaskExtra extends TaskExtra0 {
|
|||
|
||||
def andFinally(fin: => Unit): Task[S] = mapR(x => Result.tryValue[S]({ fin; x }))
|
||||
def doFinally(t: Task[Unit]): Task[S] =
|
||||
flatMapR(
|
||||
x =>
|
||||
t.result.map { tx =>
|
||||
Result.tryValues[S](tx :: Nil, x)
|
||||
}
|
||||
flatMapR(x =>
|
||||
t.result.map { tx =>
|
||||
Result.tryValues[S](tx :: Nil, x)
|
||||
}
|
||||
)
|
||||
def ||[T >: S](alt: Task[T]): Task[T] = flatMapR {
|
||||
case Value(v) => task(v: T)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ object Transform {
|
|||
pmap
|
||||
}
|
||||
|
||||
/** Applies `map`, returning the result if defined or returning the input unchanged otherwise.*/
|
||||
/** Applies `map`, returning the result if defined or returning the input unchanged otherwise. */
|
||||
implicit def getOrId(map: Task ~>| Task): Task ~> Task =
|
||||
λ[Task ~> Task](in => map(in).getOrElse(in))
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ object Test extends std.TaskExtra {
|
|||
val b2 = task(true)
|
||||
val c = task("asdf")
|
||||
|
||||
val h1 = t3(a, b, c).map { case (aa, bb, cc) => s"$aa $bb $cc" }
|
||||
val h1 = t3(a, b, c).map { case (aa, bb, cc) => s"$aa $bb $cc" }
|
||||
val h2 = t3(a, b2, c).map { case (aa, bb, cc) => s"$aa $bb $cc" }
|
||||
|
||||
type Values = (Result[Int], Result[Boolean], Result[String])
|
||||
|
|
@ -28,7 +28,9 @@ object Test extends std.TaskExtra {
|
|||
val f: Values => Any = {
|
||||
case (Value(aa), Value(bb), Value(cc)) => s"$aa $bb $cc"
|
||||
case x =>
|
||||
val cs = x.productIterator.toList.collect { case Inc(x) => x } // workaround for double definition bug
|
||||
val cs = x.productIterator.toList.collect { case Inc(x) =>
|
||||
x
|
||||
} // workaround for double definition bug
|
||||
throw Incomplete(None, causes = cs)
|
||||
}
|
||||
val d2 = t3(a, b2, c) mapR f
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort") {
|
|||
}
|
||||
}
|
||||
final def sortDirect(a: Seq[Int]): Seq[Int] = {
|
||||
if (a.length < 2)
|
||||
a
|
||||
if (a.length < 2) a
|
||||
else {
|
||||
val pivot = a(0)
|
||||
val (lt, gte) = a.view.drop(1).partition(_ < pivot)
|
||||
|
|
@ -37,8 +36,8 @@ object TaskRunnerSortTest extends Properties("TaskRunnerSort") {
|
|||
task(a) flatMap { a =>
|
||||
val pivot = a(0)
|
||||
val (lt, gte) = a.view.drop(1).partition(_ < pivot)
|
||||
sbt.Test.t2(sort(lt.toSeq), sort(gte.toSeq)) map {
|
||||
case (l, g) => l ++ List(pivot) ++ g
|
||||
sbt.Test.t2(sort(lt.toSeq), sort(gte.toSeq)) map { case (l, g) =>
|
||||
l ++ List(pivot) ++ g
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,7 @@ sealed trait CacheResult[K]
|
|||
case class Hit[O](value: O) extends CacheResult[O]
|
||||
|
||||
/**
|
||||
* A cache miss.
|
||||
* `update` associates the missing key with `O` in the cache.
|
||||
* A cache miss. `update` associates the missing key with `O` in the cache.
|
||||
*/
|
||||
case class Miss[O](update: O => Unit) extends CacheResult[O]
|
||||
|
||||
|
|
@ -42,8 +41,10 @@ object Cache {
|
|||
/**
|
||||
* Returns a function that represents a cache that inserts on miss.
|
||||
*
|
||||
* @param cacheFile The store that backs this cache.
|
||||
* @param default A function that computes a default value to insert on
|
||||
* @param cacheFile
|
||||
* The store that backs this cache.
|
||||
* @param default
|
||||
* A function that computes a default value to insert on
|
||||
*/
|
||||
def cached[I, O](cacheFile: File)(default: I => O)(implicit cache: Cache[I, O]): I => O =
|
||||
cached(CacheStore(cacheFile))(default)
|
||||
|
|
@ -51,8 +52,10 @@ object Cache {
|
|||
/**
|
||||
* Returns a function that represents a cache that inserts on miss.
|
||||
*
|
||||
* @param store The store that backs this cache.
|
||||
* @param default A function that computes a default value to insert on
|
||||
* @param store
|
||||
* The store that backs this cache.
|
||||
* @param default
|
||||
* A function that computes a default value to insert on
|
||||
*/
|
||||
def cached[I, O](store: CacheStore)(default: I => O)(implicit cache: Cache[I, O]): I => O =
|
||||
key =>
|
||||
|
|
|
|||
|
|
@ -213,13 +213,15 @@ object FileInfo {
|
|||
FileModified(file.getAbsoluteFile, lastModified)
|
||||
|
||||
/**
|
||||
* Returns an instance of [[FileModified]] where, for any directory, the maximum last
|
||||
* modified time taken from its contents is used rather than the last modified time of the
|
||||
* directory itself. The specific motivation was to prevent the doc task from re-running when
|
||||
* the modified time changed for a directory classpath but none of the classfiles had changed.
|
||||
* Returns an instance of [[FileModified]] where, for any directory, the maximum last modified
|
||||
* time taken from its contents is used rather than the last modified time of the directory
|
||||
* itself. The specific motivation was to prevent the doc task from re-running when the modified
|
||||
* time changed for a directory classpath but none of the classfiles had changed.
|
||||
*
|
||||
* @param file the file or directory
|
||||
* @return the [[FileModified]]
|
||||
* @param file
|
||||
* the file or directory
|
||||
* @return
|
||||
* the [[FileModified]]
|
||||
*/
|
||||
private[sbt] def fileOrDirectoryMax(file: File): ModifiedFileInfo = {
|
||||
val maxLastModified =
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ class PlainInput[J: IsoString](input: InputStream, converter: SupportConverter[J
|
|||
val bufferSize = 1024
|
||||
val buffer = new Array[Char](bufferSize)
|
||||
var read = 0
|
||||
while ({ read = reader.read(buffer, 0, bufferSize); read != -1 }) {
|
||||
while
|
||||
({ read = reader.read(buffer, 0, bufferSize); read != -1
|
||||
}) {
|
||||
builder.appendAll(buffer, 0, read)
|
||||
}
|
||||
builder.toString()
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ object StampedFormat extends BasicJsonProtocol {
|
|||
withStamp(stamp(format))(format)
|
||||
}
|
||||
|
||||
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(
|
||||
implicit formatStamp: JsonFormat[S],
|
||||
def withStamp[T, S](stamp: S)(format: JsonFormat[T])(implicit
|
||||
formatStamp: JsonFormat[S],
|
||||
equivStamp: Equiv[S]
|
||||
): JsonFormat[T] =
|
||||
new JsonFormat[T] {
|
||||
|
|
|
|||
|
|
@ -17,60 +17,56 @@ import org.scalatest.flatspec.AnyFlatSpec
|
|||
class CacheSpec extends AnyFlatSpec {
|
||||
|
||||
"A cache" should "NOT throw an exception if read without being written previously" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(_) => ()
|
||||
}
|
||||
testCache[String, Int] { case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(_) => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a very simple value" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(5)
|
||||
}
|
||||
testCache[String, Int] { case (cache, store) =>
|
||||
cache(store)("missing") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "be updatable" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
val value = 5
|
||||
cache(store)("someKey") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
testCache[String, Int] { case (cache, store) =>
|
||||
val value = 5
|
||||
cache(store)("someKey") match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
|
||||
cache(store)("someKey") match {
|
||||
case Hit(read) => assert(read === value); ()
|
||||
case Miss(_) => fail()
|
||||
}
|
||||
cache(store)("someKey") match {
|
||||
case Hit(read) => assert(read === value); ()
|
||||
case Miss(_) => fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "return the value that has been previously written" in {
|
||||
testCache[String, Int] {
|
||||
case (cache, store) =>
|
||||
val key = "someKey"
|
||||
val value = 5
|
||||
cache(store)(key) match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
testCache[String, Int] { case (cache, store) =>
|
||||
val key = "someKey"
|
||||
val value = 5
|
||||
cache(store)(key) match {
|
||||
case Hit(_) => fail()
|
||||
case Miss(update) => update(value)
|
||||
}
|
||||
|
||||
cache(store)(key) match {
|
||||
case Hit(read) => assert(read === value); ()
|
||||
case Miss(_) => fail()
|
||||
}
|
||||
cache(store)(key) match {
|
||||
case Hit(read) => assert(read === value); ()
|
||||
case Miss(_) => fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(
|
||||
implicit cache: Cache[K, V]
|
||||
private def testCache[K, V](f: (Cache[K, V], CacheStore) => Unit)(implicit
|
||||
cache: Cache[K, V]
|
||||
): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store")
|
||||
|
|
|
|||
|
|
@ -47,46 +47,42 @@ class SingletonCacheSpec extends AnyFlatSpec {
|
|||
}
|
||||
|
||||
"A singleton cache" should "throw an exception if read without being written previously" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
intercept[Exception] {
|
||||
cache.read(store)
|
||||
}
|
||||
()
|
||||
testCache[Int] { case (cache, store) =>
|
||||
intercept[Exception] {
|
||||
cache.read(store)
|
||||
}
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a very simple value" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
cache.write(store, 5)
|
||||
testCache[Int] { case (cache, store) =>
|
||||
cache.write(store, 5)
|
||||
}
|
||||
}
|
||||
|
||||
it should "return the simple value that has been previously written" in {
|
||||
testCache[Int] {
|
||||
case (cache, store) =>
|
||||
val value = 5
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
testCache[Int] { case (cache, store) =>
|
||||
val value = 5
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
|
||||
assert(read === value); ()
|
||||
assert(read === value); ()
|
||||
}
|
||||
}
|
||||
|
||||
it should "write a complex value" in {
|
||||
testCache[ComplexType] {
|
||||
case (cache, store) =>
|
||||
val value = ComplexType(1, "hello, world!", (1 to 10 by 3).toList)
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
testCache[ComplexType] { case (cache, store) =>
|
||||
val value = ComplexType(1, "hello, world!", (1 to 10 by 3).toList)
|
||||
cache.write(store, value)
|
||||
val read = cache.read(store)
|
||||
|
||||
assert(read === value); ()
|
||||
assert(read === value); ()
|
||||
}
|
||||
}
|
||||
|
||||
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(
|
||||
implicit cache: SingletonCache[T]
|
||||
private def testCache[T](f: (SingletonCache[T], CacheStore) => Unit)(implicit
|
||||
cache: SingletonCache[T]
|
||||
): Unit =
|
||||
IO.withTemporaryDirectory { tmp =>
|
||||
val store = new FileBasedStore(tmp / "cache-store")
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ object Util {
|
|||
|
||||
def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) s"`${s}`" else s
|
||||
|
||||
// def ignoreResult[T](f: => T): Unit = macro Macro.ignore
|
||||
def ignoreResult[A](f: => A): Unit =
|
||||
f; ()
|
||||
|
||||
lazy val isMac: Boolean =
|
||||
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac")
|
||||
|
|
|
|||
|
|
@ -22,31 +22,32 @@ object ChangeReport {
|
|||
}
|
||||
}
|
||||
|
||||
/** The result of comparing some current set of objects against a previous set of objects.*/
|
||||
/** The result of comparing some current set of objects against a previous set of objects. */
|
||||
trait ChangeReport[T] {
|
||||
|
||||
/** The set of all of the objects in the current set.*/
|
||||
/** The set of all of the objects in the current set. */
|
||||
def checked: Set[T]
|
||||
|
||||
/** All of the objects that are in the same state in the current and reference sets.*/
|
||||
/** All of the objects that are in the same state in the current and reference sets. */
|
||||
def unmodified: Set[T]
|
||||
|
||||
/**
|
||||
* All checked objects that are not in the same state as the reference. This includes objects that are in both
|
||||
* sets but have changed and files that are only in one set.
|
||||
* All checked objects that are not in the same state as the reference. This includes objects that
|
||||
* are in both sets but have changed and files that are only in one set.
|
||||
*/
|
||||
def modified: Set[T] // all changes, including added
|
||||
|
||||
/** All objects that are only in the current set.*/
|
||||
/** All objects that are only in the current set. */
|
||||
def added: Set[T]
|
||||
|
||||
/** All objects only in the previous set*/
|
||||
/** All objects only in the previous set */
|
||||
def removed: Set[T]
|
||||
def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other)
|
||||
|
||||
/**
|
||||
* Generate a new report with this report's unmodified set included in the new report's modified set. The new report's
|
||||
* unmodified set is empty. The new report's added, removed, and checked sets are the same as in this report.
|
||||
* Generate a new report with this report's unmodified set included in the new report's modified
|
||||
* set. The new report's unmodified set is empty. The new report's added, removed, and checked
|
||||
* sets are the same as in this report.
|
||||
*/
|
||||
def markAllModified: ChangeReport[T] =
|
||||
new ChangeReport[T] {
|
||||
|
|
|
|||
|
|
@ -15,40 +15,41 @@ object FileFunction {
|
|||
private val defaultOutStyle = FileInfo.exists
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param cacheBaseDirectory The folder in which to store
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param cacheBaseDirectory
|
||||
* The folder in which to store
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File)(action: Set[File] => Set[File]): Set[File] => Set[File] =
|
||||
cached(cacheBaseDirectory, inStyle = defaultInStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param cacheBaseDirectory The folder in which to store
|
||||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param cacheBaseDirectory
|
||||
* The folder in which to store
|
||||
* @param inStyle
|
||||
* The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style)(
|
||||
action: Set[File] => Set[File]
|
||||
|
|
@ -56,65 +57,68 @@ object FileFunction {
|
|||
cached(cacheBaseDirectory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param cacheBaseDirectory The folder in which to store
|
||||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param outStyle The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param cacheBaseDirectory
|
||||
* The folder in which to store
|
||||
* @param inStyle
|
||||
* The strategy by which to detect state change in the input files from the previous run
|
||||
* @param outStyle
|
||||
* The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(cacheBaseDirectory: File, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
|
||||
action: Set[File] => Set[File]
|
||||
): Set[File] => Set[File] =
|
||||
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)(
|
||||
(in, out) => action(in.checked)
|
||||
cached(CacheStoreFactory(cacheBaseDirectory), inStyle, outStyle)((in, out) =>
|
||||
action(in.checked)
|
||||
)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param storeFactory The factory to use to get stores for the input and output files.
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param storeFactory
|
||||
* The factory to use to get stores for the input and output files.
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(storeFactory: CacheStoreFactory)(action: UpdateFunction): Set[File] => Set[File] =
|
||||
cached(storeFactory, inStyle = defaultInStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param storeFactory The factory to use to get stores for the input and output files.
|
||||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param storeFactory
|
||||
* The factory to use to get stores for the input and output files.
|
||||
* @param inStyle
|
||||
* The strategy by which to detect state change in the input files from the previous run
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style)(
|
||||
action: UpdateFunction
|
||||
|
|
@ -122,22 +126,24 @@ object FileFunction {
|
|||
cached(storeFactory, inStyle = inStyle, outStyle = defaultOutStyle)(action)
|
||||
|
||||
/**
|
||||
* Generic change-detection helper used to help build / artifact generation /
|
||||
* etc. steps detect whether or not they need to run. Returns a function whose
|
||||
* input is a Set of input files, and subsequently executes the action function
|
||||
* (which does the actual work: compiles, generates resources, etc.), returning
|
||||
* a Set of output files that it generated.
|
||||
* Generic change-detection helper used to help build / artifact generation / etc. steps detect
|
||||
* whether or not they need to run. Returns a function whose input is a Set of input files, and
|
||||
* subsequently executes the action function (which does the actual work: compiles, generates
|
||||
* resources, etc.), returning a Set of output files that it generated.
|
||||
*
|
||||
* The input file and resulting output file state is cached in stores issued by
|
||||
* `storeFactory`. On each invocation, the state of the input and output
|
||||
* files from the previous run is compared against the cache, as is the set of
|
||||
* input files. If a change in file state / input files set is detected, the
|
||||
* action function is re-executed.
|
||||
* The input file and resulting output file state is cached in stores issued by `storeFactory`. On
|
||||
* each invocation, the state of the input and output files from the previous run is compared
|
||||
* against the cache, as is the set of input files. If a change in file state / input files set is
|
||||
* detected, the action function is re-executed.
|
||||
*
|
||||
* @param storeFactory The factory to use to get stores for the input and output files.
|
||||
* @param inStyle The strategy by which to detect state change in the input files from the previous run
|
||||
* @param outStyle The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action The work function, which receives a list of input files and returns a list of output files
|
||||
* @param storeFactory
|
||||
* The factory to use to get stores for the input and output files.
|
||||
* @param inStyle
|
||||
* The strategy by which to detect state change in the input files from the previous run
|
||||
* @param outStyle
|
||||
* The strategy by which to detect state change in the output files from the previous run
|
||||
* @param action
|
||||
* The work function, which receives a list of input files and returns a list of output files
|
||||
*/
|
||||
def cached(storeFactory: CacheStoreFactory, inStyle: FileInfo.Style, outStyle: FileInfo.Style)(
|
||||
action: UpdateFunction
|
||||
|
|
|
|||
|
|
@ -20,22 +20,22 @@ import sjsonnew.support.murmurhash.Hasher
|
|||
object Tracked {
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If the function throws an exception.
|
||||
* Creates a tracker that provides the last time it was evaluated. If the function throws an
|
||||
* exception.
|
||||
*/
|
||||
def tstamp(store: CacheStore): Timestamp = tstamp(store, true)
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If the function throws an exception.
|
||||
* Creates a tracker that provides the last time it was evaluated. If the function throws an
|
||||
* exception.
|
||||
*/
|
||||
def tstamp(cacheFile: File): Timestamp = tstamp(CacheStore(cacheFile))
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If 'useStartTime' is true, the recorded time is the start of the evaluated function.
|
||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.
|
||||
* Creates a tracker that provides the last time it was evaluated. If 'useStartTime' is true, the
|
||||
* recorded time is the start of the evaluated function. If 'useStartTime' is false, the recorded
|
||||
* time is when the evaluated function completes. In both cases, the timestamp is not updated if
|
||||
* the function throws an exception.
|
||||
*/
|
||||
def tstamp(store: CacheStore, useStartTime: Boolean): Timestamp = {
|
||||
import CacheImplicits.LongJsonFormat
|
||||
|
|
@ -43,27 +43,39 @@ object Tracked {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a tracker that provides the last time it was evaluated.
|
||||
* If 'useStartTime' is true, the recorded time is the start of the evaluated function.
|
||||
* If 'useStartTime' is false, the recorded time is when the evaluated function completes.
|
||||
* In both cases, the timestamp is not updated if the function throws an exception.
|
||||
* Creates a tracker that provides the last time it was evaluated. If 'useStartTime' is true, the
|
||||
* recorded time is the start of the evaluated function. If 'useStartTime' is false, the recorded
|
||||
* time is when the evaluated function completes. In both cases, the timestamp is not updated if
|
||||
* the function throws an exception.
|
||||
*/
|
||||
def tstamp(cacheFile: File, useStartTime: Boolean): Timestamp =
|
||||
tstamp(CacheStore(cacheFile), useStartTime)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
||||
/**
|
||||
* Creates a tracker that provides the difference between a set of input files for successive
|
||||
* invocations.
|
||||
*/
|
||||
def diffInputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.inputs(store, style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of input files for successive invocations.*/
|
||||
/**
|
||||
* Creates a tracker that provides the difference between a set of input files for successive
|
||||
* invocations.
|
||||
*/
|
||||
def diffInputs(cacheFile: File, style: FileInfo.Style): Difference =
|
||||
diffInputs(CacheStore(cacheFile), style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
||||
/**
|
||||
* Creates a tracker that provides the difference between a set of output files for successive
|
||||
* invocations.
|
||||
*/
|
||||
def diffOutputs(store: CacheStore, style: FileInfo.Style): Difference =
|
||||
Difference.outputs(store, style)
|
||||
|
||||
/** Creates a tracker that provides the difference between a set of output files for successive invocations.*/
|
||||
/**
|
||||
* Creates a tracker that provides the difference between a set of output files for successive
|
||||
* invocations.
|
||||
*/
|
||||
def diffOutputs(cacheFile: File, style: FileInfo.Style): Difference =
|
||||
diffOutputs(CacheStore(cacheFile), style)
|
||||
|
||||
|
|
@ -113,7 +125,8 @@ object Tracked {
|
|||
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
|
||||
* }}}
|
||||
*
|
||||
* This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1: JsonFormat`.
|
||||
* This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1:
|
||||
* JsonFormat`.
|
||||
*/
|
||||
def outputChangedW[A1: JsonWriter, A2](store: CacheStore)(
|
||||
f: (Boolean, A1) => A2
|
||||
|
|
@ -163,7 +176,8 @@ object Tracked {
|
|||
* cachedDoc(inputs)(() => exists(outputDirectory.allPaths.get.toSet))
|
||||
* }}}
|
||||
*
|
||||
* This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1: JsonFormat`.
|
||||
* This is a variant of `outputChanged` that takes `A1: JsonWriter` as opposed to `A1:
|
||||
* JsonFormat`.
|
||||
*/
|
||||
def outputChangedW[A1: JsonWriter, A2](
|
||||
cacheFile: File
|
||||
|
|
@ -298,7 +312,7 @@ object Tracked {
|
|||
|
||||
trait Tracked {
|
||||
|
||||
/** Cleans outputs and clears the cache.*/
|
||||
/** Cleans outputs and clears the cache. */
|
||||
def clean(): Unit
|
||||
|
||||
}
|
||||
|
|
@ -308,8 +322,8 @@ class Timestamp(val store: CacheStore, useStartTime: Boolean)(implicit format: J
|
|||
def clean() = store.delete()
|
||||
|
||||
/**
|
||||
* Reads the previous timestamp, evaluates the provided function,
|
||||
* and then updates the timestamp if the function completes normally.
|
||||
* Reads the previous timestamp, evaluates the provided function, and then updates the timestamp
|
||||
* if the function completes normally.
|
||||
*/
|
||||
def apply[T](f: Long => T): T = {
|
||||
val start = now()
|
||||
|
|
@ -329,8 +343,7 @@ class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
|
|||
def clean() = store.delete()
|
||||
|
||||
def apply[O2](ifChanged: O => O2, ifUnchanged: O => O2): O => O2 = value => {
|
||||
if (uptodate(value))
|
||||
ifUnchanged(value)
|
||||
if (uptodate(value)) ifUnchanged(value)
|
||||
else {
|
||||
update(value)
|
||||
ifChanged(value)
|
||||
|
|
@ -338,7 +351,9 @@ class Changed[O: Equiv: JsonFormat](val store: CacheStore) extends Tracked {
|
|||
}
|
||||
|
||||
def update(value: O): Unit =
|
||||
store.write(value) //Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
store.write(
|
||||
value
|
||||
) // Using.fileOutputStream(false)(cacheFile)(stream => format.writes(stream, value))
|
||||
|
||||
def uptodate(value: O): Boolean = {
|
||||
val equiv: Equiv[O] = implicitly
|
||||
|
|
@ -354,9 +369,10 @@ object Difference {
|
|||
(store, style) => new Difference(store, style, defineClean, filesAreOutputs)
|
||||
|
||||
/**
|
||||
* Provides a constructor for a Difference that removes the files from the previous run on a call to 'clean' and saves the
|
||||
* hash/last modified time of the files as they are after running the function. This means that this information must be evaluated twice:
|
||||
* before and after running the function.
|
||||
* Provides a constructor for a Difference that removes the files from the previous run on a call
|
||||
* to 'clean' and saves the hash/last modified time of the files as they are after running the
|
||||
* function. This means that this information must be evaluated twice: before and after running
|
||||
* the function.
|
||||
*/
|
||||
val outputs = constructor(true, true)
|
||||
|
||||
|
|
@ -405,8 +421,10 @@ class Difference(
|
|||
|
||||
val report = new ChangeReport[File] {
|
||||
lazy val checked = currentFiles
|
||||
lazy val removed = lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added = checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val removed =
|
||||
lastFiles -- checked // all files that were included previously but not this time. This is independent of whether the files exist.
|
||||
lazy val added =
|
||||
checked -- lastFiles // all files included now but not previously. This is independent of whether the files exist.
|
||||
lazy val modified = raw(lastFilesInfo -- currentFilesInfo.files) ++ added
|
||||
lazy val unmodified = checked -- modified
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ class TrackedSpec extends AnyFlatSpec {
|
|||
withStore { store =>
|
||||
val input0 = Input(1)
|
||||
|
||||
val cachedFun = Tracked.inputChangedW[Input, Int](store) {
|
||||
case (_, in) => in.v
|
||||
val cachedFun = Tracked.inputChangedW[Input, Int](store) { case (_, in) =>
|
||||
in.v
|
||||
}
|
||||
|
||||
val res0 = cachedFun(input0)
|
||||
|
|
@ -145,8 +145,8 @@ class TrackedSpec extends AnyFlatSpec {
|
|||
withStore { store =>
|
||||
val input0 = Input(1)
|
||||
|
||||
val cachedFun = Tracked.outputChangedW[Input, Int](store) {
|
||||
case (_, in) => in.v
|
||||
val cachedFun = Tracked.outputChangedW[Input, Int](store) { case (_, in) =>
|
||||
in.v
|
||||
}
|
||||
|
||||
val res0 = cachedFun(() => input0)
|
||||
|
|
|
|||
Loading…
Reference in New Issue