Port util-logging

This commit is contained in:
Eugene Yokota 2021-12-20 02:00:30 -05:00
parent c724e83fd1
commit 50b062b795
64 changed files with 1115 additions and 884 deletions

View File

@ -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()

View File

@ -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.",

View File

@ -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 =

View File

@ -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,

View File

@ -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)))

View File

@ -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 })

View File

@ -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)

View File

@ -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)))

View File

@ -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 == '_')

View File

@ -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)
}
}
}

View File

@ -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 == '$'

View File

@ -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.")

View File

@ -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))

View File

@ -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 =>

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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() }

View File

@ -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])
})
}
}

View File

@ -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); () }

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 = {

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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. */

View File

@ -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._

View File

@ -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)

View File

@ -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 =

View File

@ -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?

View File

@ -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

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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. */

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 =

View File

@ -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] = {

View File

@ -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,

View File

@ -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)
()
}

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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 =>

View File

@ -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 =

View File

@ -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()

View File

@ -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] {

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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] {

View File

@ -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

View File

@ -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
}

View File

@ -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)