Apply Scalafmt format (Scala 3 syntax)

This commit is contained in:
Eugene Yokota
2026-09-10 14:16:27 -04:00
parent ace09d39e3
commit f9688c1464
598 changed files with 9294 additions and 13894 deletions
+25 -39
View File
@@ -82,13 +82,13 @@ def commonSettings: Seq[Setting[?]] = Def.settings(
Global / concurrentRestrictions += Utils.testExclusiveRestriction,
// On Windows, limit to one task at a time to avoid OverlappingFileLockException when
// multiple tasks (e.g. scalafix plugin and sbt Coursier) write to the same cache.
Global / concurrentRestrictions ++= (if (scala.util.Properties.isWin) Seq(Tags.limitAll(1))
Global / concurrentRestrictions ++= (if scala.util.Properties.isWin then Seq(Tags.limitAll(1))
else Nil),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-w", "1"),
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2"),
compile / javacOptions ++= Seq("-Xlint", "-Xlint:-serial"),
Compile / doc / scalacOptions ++= {
if (Dependencies.sbtIoPath.isEmpty && Dependencies.sbtZincPath.isEmpty) {
if Dependencies.sbtIoPath.isEmpty && Dependencies.sbtZincPath.isEmpty then
import scala.sys.process.*
val devnull = ProcessLogger(_ => ())
val tagOrSha =
@@ -98,9 +98,7 @@ def commonSettings: Seq[Setting[?]] = Def.settings(
"-revision",
tagOrSha
)
} else {
Nil
}
else Nil
},
Compile / javafmtOnCompile := scalafmtOnCompile.value,
Test / javafmtOnCompile := (Test / scalafmtOnCompile).value,
@@ -132,8 +130,8 @@ val sbt20Plus =
val mimaSettings = mimaSettingsSince(sbt20Plus)
def mimaSettingsSince(versions: Seq[String]): Seq[Def.Setting[?]] = Def.settings(
mimaPreviousArtifacts := {
val crossVersion = if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled
if (sbtPlugin.value) {
val crossVersion = if crossPaths.value then CrossVersion.binary else CrossVersion.disabled
if sbtPlugin.value then
versions
.map(v =>
Defaults.sbtPluginExtra(
@@ -143,9 +141,7 @@ def mimaSettingsSince(versions: Seq[String]): Seq[Def.Setting[?]] = Def.settings
)
)
.toSet
} else {
versions.map(v => (organization.value % moduleName.value % v).cross(crossVersion)).toSet
}
else versions.map(v => (organization.value % moduleName.value % v).cross(crossVersion)).toSet
},
mimaBinaryIssueFilters ++= Seq(
),
@@ -183,8 +179,7 @@ lazy val sbtRoot: Project = (project in file("."))
| /____/_.___/\__/
|Welcome to the build for sbt.
|""".stripMargin +
(if (version != "17")
s"""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
(if version != "17" then s"""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
| Java version is $version. We recommend java 17.
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!""".stripMargin
else "")
@@ -204,16 +199,14 @@ lazy val sbtRoot: Project = (project in file("."))
installNativeThinClient := {
// nativeInstallDirectory can be set globally or in a gitignored local file
val dir = nativeInstallDirectory.?.value
val target = Def.spaceDelimited("").parsed.headOption match {
val target = Def.spaceDelimited("").parsed.headOption match
case Some(p) => file(p).toPath
case _ =>
dir match {
dir match
case Some(d) => d / "sbtn"
case _ =>
val msg = "Expected input parameter <path>: installNativeExecutable /usr/local/bin"
throw new IllegalStateException(msg)
}
}
val base = baseDirectory.value.toPath
val exec = fileConverter.value.toPath((sbtClientProj / nativeImage).value)
streams.value.log.info(s"installing thin client ${base.relativize(exec)} to ${target}")
@@ -351,7 +344,7 @@ lazy val utilLogging = project
Compile / generateContrabands / contrabandFormatsForType := { tpe =>
val old = (Compile / generateContrabands / contrabandFormatsForType).value
val name = tpe.removeTypeParameters.name
if (name == "Throwable") Nil
if name == "Throwable" then Nil
else old(tpe)
},
Test / fork := true,
@@ -905,9 +898,9 @@ lazy val serverTestProj = (project in file("server-test"))
.map(_.toFile.getAbsolutePath)
.mkString(java.io.File.pathSeparator)
val cp =
if (scala.util.Properties.isWin) rawClasspath.replace("\\", "\\\\")
if scala.util.Properties.isWin then rawClasspath.replace("\\", "\\\\")
else rawClasspath
val content = {
val content =
s"""|
|package testpkg
|
@@ -917,7 +910,6 @@ lazy val serverTestProj = (project in file("server-test"))
| val scalaVersion = "${scalaVersion.value}"
|}
""".stripMargin
}
val file =
(Test / target).value / "generated" / "src" / "test" / "scala" / "testpkg" / "TestProperties.scala"
IO.write(file, content)
@@ -956,17 +948,14 @@ lazy val sbtClientProj = (project in file("client"))
nativeImageJvm := "graalvm-java23",
nativeImageOutput := {
val outputDir = (target.value / "bin").toPath
if (!Files.exists(outputDir)) {
Files.createDirectories(outputDir)
}
if !Files.exists(outputDir) then Files.createDirectories(outputDir)
fileConverter.value.toVirtualFile(outputDir.resolve("sbtn"))
},
nativeImageCommand := {
val orig = nativeImageCommand.value
sys.env.get("ARCHS") match {
sys.env.get("ARCHS") match
case Some(a) => Seq("arch", s"-$a") ++ orig
case None => orig
}
},
nativeImageOptions ++= Seq(
"--no-fallback",
@@ -983,16 +972,16 @@ lazy val sbtClientProj = (project in file("client"))
),
buildThinClient := {
val isFish = Def.spaceDelimited("").parsed.headOption.fold(false)(_ == "--fish")
val ext = if (isWin) ".bat" else if (isFish) ".fish" else ".sh"
val output = target.value.toPath / "bin" / s"${if (isFish) "fish-" else ""}client$ext"
val ext = if isWin then ".bat" else if isFish then ".fish" else ".sh"
val output = target.value.toPath / "bin" / s"${if isFish then "fish-" else ""}client$ext"
java.nio.file.Files.createDirectories(output.getParent)
val cp = (Compile / fullClasspathAsJars).value.map(_.data)
val args =
if (isWin) "%*" else if (isFish) s"$$argv" else s"$$*"
if isWin then "%*" else if isFish then s"$$argv" else s"$$*"
java.nio.file.Files.write(
output,
s"""
|${if (isWin) "@echo off" else s"#!/usr/bin/env ${if (isFish) "fish" else "sh"}"}
|${if isWin then "@echo off" else s"#!/usr/bin/env ${if isFish then "fish" else "sh"}"}
|
|java -cp ${cp.mkString(java.io.File.pathSeparator)} sbt.client.Client --jna $args
""".stripMargin.linesIterator.toSeq.tail.mkString("\n").getBytes
@@ -1058,11 +1047,10 @@ lazy val upperModules = (project in (file("internal") / "upper"))
Utils.noPublish
)
lazy val sbtIgnoredProblems = {
lazy val sbtIgnoredProblems =
import com.typesafe.tools.mima.core.*
Vector(
)
}
def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTask {
val _ = publishLocalBinAll.value
@@ -1075,7 +1063,7 @@ def scriptedTask(launch: Boolean): Def.Initialize[InputTask[Unit]] = Def.inputTa
scriptedBufferLog.value,
Def.setting(Scripted.scriptedParser(scriptedSource.value)).parsed,
scriptedPrescripted.value,
scriptedLaunchOpts.value ++ (if (launch) Some(launchJar) else None),
scriptedLaunchOpts.value ++ (if launch then Some(launchJar) else None),
scalaVersion.value,
version.value,
(scriptedSbtProj / Test / fullClasspathAsJars).value
@@ -1171,10 +1159,9 @@ def otherRootSettings =
"-server",
s"-Dsbt.build.root=${(ThisBuild / baseDirectory).value.getAbsolutePath}"
) :::
(sys.props.get("sbt.ivy.home") match {
(sys.props.get("sbt.ivy.home") match
case Some(home) => List(s"-Dsbt.ivy.home=$home")
case _ => Nil
}),
case _ => Nil),
publishLocalBinAll := {
val _ = (Compile / publishLocalBin).all(scriptedProjects).value
},
@@ -1185,10 +1172,9 @@ def otherRootSettings =
"-Xms512M",
"-server"
) :::
(sys.props.get("sbt.ivy.home") match {
(sys.props.get("sbt.ivy.home") match
case Some(home) => List(s"-Dsbt.ivy.home=$home")
case _ => Nil
}),
case _ => Nil),
scripted := scriptedTask(true).evaluated,
scriptedUnpublished := scriptedTask(true).evaluated,
scriptedSource := (sbtProj / sourceDirectory).value / "repo-override-test"
@@ -1248,7 +1234,7 @@ ThisBuild / pomIncludeRepository := (_ => false) // drop repos other than Maven
ThisBuild / publishTo := {
val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
val v = (ThisBuild / version).value
if (v.endsWith("SNAPSHOT")) Some("central-snapshots" at centralSnapshots)
if v.endsWith("SNAPSHOT") then Some("central-snapshots" at centralSnapshots)
else localStaging.value
}
ThisBuild / publishMavenStyle := true
@@ -16,72 +16,59 @@ import sbt.librarymanagement.Configuration
import Def.*
/** This represents a `Setting` expression configured by the sbt DSL. */
sealed trait DslEntry {
sealed trait DslEntry:
/** Called by the parser. Sets the position where this entry was defined in the build.sbt file. */
def withPos(pos: RangePosition): DslEntry
}
object DslEntry {
object DslEntry:
implicit def fromSettingsDef(inc: SettingsDefinition): DslEntry =
DslSetting(inc)
implicit def fromSettingsDef(inc: Seq[Setting[?]]): DslEntry =
DslSetting(inc)
/** Represents a DSL entry which adds settings to the current project. */
sealed trait ProjectSettings extends DslEntry {
sealed trait ProjectSettings extends DslEntry:
def toSettings: Seq[Setting[?]]
}
object ProjectSettings {
object ProjectSettings:
def unapply(e: DslEntry): Option[Seq[Setting[?]]] =
e match {
e match
case e: ProjectSettings => Some(e.toSettings)
case _ => None
}
}
/** Represents a DSL entry which manipulates the current project. */
sealed trait ProjectManipulation extends DslEntry {
sealed trait ProjectManipulation extends DslEntry:
def toFunction: Project => Project
// TODO - Should we store this?
final def withPos(pos: RangePosition): DslEntry = this
}
object ProjectManipulation {
object ProjectManipulation:
def unapply(e: DslEntry): Option[Project => Project] =
e match {
e match
case e: ProjectManipulation => Some(e.toFunction)
case _ => None
}
}
/** this represents an actually Setting[_] or Seq[Setting[_]] configured by the sbt DSL. */
case class DslSetting(settings: SettingsDefinition) extends ProjectSettings {
case class DslSetting(settings: SettingsDefinition) extends ProjectSettings:
def toSettings = settings.settings
final def withPos(pos: RangePosition): DslEntry =
DslSetting(settings.settings.map(_.withPos(pos)))
}
/** this represents an `enablePlugins()` in the sbt DSL */
case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation {
case class DslEnablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation:
override val toFunction: Project => Project = _.enablePlugins(plugins*)
}
/** this represents an `disablePlugins()` in the sbt DSL */
case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation {
case class DslDisablePlugins(plugins: Seq[AutoPlugin]) extends ProjectManipulation:
override val toFunction: Project => Project = _.disablePlugins(plugins*)
}
/** Represents registering an internal dependency for the current project */
case class DslDependsOn(cs: Seq[ClasspathDep[ProjectReference]]) extends ProjectManipulation {
case class DslDependsOn(cs: Seq[ClasspathDep[ProjectReference]]) extends ProjectManipulation:
override val toFunction: Project => Project = _.dependsOn(cs*)
}
/** Represents registering a set of configurations with the current project. */
case class DslConfigs(cs: Seq[Configuration]) extends ProjectManipulation {
case class DslConfigs(cs: Seq[Configuration]) extends ProjectManipulation:
override val toFunction: Project => Project = _.configs(cs*)
}
/** this represents an `aggregateProjects()` in the sbt DSL */
case class DslAggregate(refs: Seq[ProjectReference]) extends ProjectManipulation {
case class DslAggregate(refs: Seq[ProjectReference]) extends ProjectManipulation:
override val toFunction: Project => Project = _.aggregate(refs*)
}
}
end DslEntry
@@ -255,6 +255,7 @@ class Eval(
generated = generatedFiles,
enclosingModule = moduleName,
)
end evalCommon
// location of the cached type or definition information
private def cacheFile(base: Path, moduleName: String): Path =
@@ -271,12 +272,12 @@ class Eval(
val source = ev.makeSource(moduleName)
run.compileSources(source :: Nil)
val unit = run.units.head
val traverser = new tpd.TreeTraverser {
override def traverse(tree: tpd.Tree)(using Context): Unit = {
tree match {
val traverser = new tpd.TreeTraverser:
override def traverse(tree: tpd.Tree)(using Context): Unit =
tree match
case x: tpd.TypeDef
if x.name.mangledString == s"${moduleName}${NameTransformer.MODULE_SUFFIX_STRING}" =>
x.rhs match {
x.rhs match
case template: tpd.Template =>
template.body.foreach {
case defdef: tpd.DefDef if defdef.name.mangledString == WrapValName =>
@@ -314,13 +315,9 @@ class Eval(
case _ =>
}
case _ =>
}
case _: tpd.PackageDef =>
traverseChildren(tree)
case _ =>
}
}
}
traverser.traverse(unit.tpdTree)
checkError("an error in expression")
val extra: A = ev.extract(run, unit)
@@ -329,6 +326,7 @@ class Eval(
}
val loader = (parent: ClassLoader) => AbstractFileClassLoader(outputDir, parent)
(extra, loader)
end compileAndLoad
private final class EvalIntermediate[A](
val extra: A,
@@ -534,11 +532,10 @@ final class EvalDefinitions(
val enclosingModule: String,
val valNames: Seq[String]
):
def values(parent: ClassLoader): Seq[Any] = {
def values(parent: ClassLoader): Seq[Any] =
val module = Eval.getModule(enclosingModule, loader(parent))
for n <- valNames
yield module.getClass.getMethod(n).invoke(module)
}
end EvalDefinitions
final class EvalException(msg: String) extends RuntimeException(msg)
@@ -33,7 +33,7 @@ import dotty.tools.dotc.ast.untpd.{ Annotated, ValOrDefDef, Tree }
* 2. Compiling scala code into local .class files
* 3. Evaluating the expressions and obtaining in-memory objects of the results (Setting[_] instances, or val references).
*/
private[sbt] object EvaluateConfigurations {
private[sbt] object EvaluateConfigurations:
type LazyClassLoaded[A] = ClassLoader => A
@@ -61,7 +61,7 @@ private[sbt] object EvaluateConfigurations {
eval: Eval,
srcs: Seq[VirtualFile],
imports: Seq[String],
): LazyClassLoaded[LoadedSbtFile] = {
): LazyClassLoaded[LoadedSbtFile] =
val loadFiles = srcs.sortBy(_.name) map { src =>
evaluateSbtFile(eval, src, IO.readStream(src.input()).linesIterator.toList, imports, 0)
}
@@ -69,7 +69,6 @@ private[sbt] object EvaluateConfigurations {
loadFiles.foldLeft(LoadedSbtFile.empty) { (loaded, load) =>
loaded.merge(load(loader))
}
}
/**
* Reads a given .sbt file and evaluates it into a sequence of setting values.
@@ -95,7 +94,7 @@ private[sbt] object EvaluateConfigurations {
builtinImports: Seq[String],
offset: Int,
options: Seq[String]
): ParsedFile = {
): ParsedFile =
def loseTree(l: (String, Tree, LineRange)): (String, LineRange) = (l._1, l._3)
val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines, options)
val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements)
@@ -103,7 +102,6 @@ private[sbt] object EvaluateConfigurations {
addOffsetToRange(offset, settingsAndDefinitions)
)
new ParsedFile(allImports, definitions.map(loseTree), settings.map(loseTree))
}
/**
* Evaluates a parsed sbt configuration file.
@@ -121,10 +119,9 @@ private[sbt] object EvaluateConfigurations {
lines: Seq[String],
imports: Seq[String],
offset: Int
): LazyClassLoaded[Seq[Setting[?]]] = {
): LazyClassLoaded[Seq[Setting[?]]] =
val l = evaluateSbtFile(eval, file, lines, imports, offset)
loader => l(loader).settings
}
/**
* Evaluates a parsed sbt configuration file.
@@ -142,7 +139,7 @@ private[sbt] object EvaluateConfigurations {
lines: Seq[String],
imports: Seq[String],
offset: Int
): LazyClassLoaded[LoadedSbtFile] = {
): LazyClassLoaded[LoadedSbtFile] =
// TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do
// detection for which project manipulations should be applied.
val name = file match
@@ -150,13 +147,12 @@ private[sbt] object EvaluateConfigurations {
case file => file.id
val parsed = parseConfiguration(file, lines, imports, offset, eval.nonCpOptions)
val (importDefs, definitions) =
if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty)
else {
if parsed.definitions.isEmpty then (Nil, DefinedSbtValues.empty)
else
val definitions =
evaluateDefinitions(eval, name, parsed.imports, parsed.definitions, Some(file))
val imp = BuildUtilLite.importAllRoot(definitions.enclosingModule :: Nil)
(imp, DefinedSbtValues(definitions))
}
val allImports = importDefs.map(s => (s, -1)) ++ parsed.imports
val dslEntries = parsed.settings map { (dslExpression, range) =>
evaluateDslEntry(eval, name, allImports, dslExpression, range)
@@ -167,13 +163,12 @@ private[sbt] object EvaluateConfigurations {
// Tracks all the files we generated from evaluating the sbt file.
val allGeneratedFiles: Seq[Path] = (definitions.generated ++ dslEntries.flatMap(_.generated))
loader => {
val projects = {
loader =>
val projects =
val compositeProjects = definitions
.values(loader)
.collect { case p: CompositeProject => p }
CompositeProject.expand(compositeProjects)
}
val loadedDslEntries = dslEntries.map(_.result.apply(loader))
val settings = loadedDslEntries.collect { case DslEntry.ProjectSettings(s) => s }.flatten
val manipulations = loadedDslEntries.collect { case DslEntry.ProjectManipulation(f) => f }
@@ -186,8 +181,7 @@ private[sbt] object EvaluateConfigurations {
definitions,
allGeneratedFiles
)
}
}
end evaluateSbtFile
private def addOffset(offset: Int, lines: Seq[(String, Int)]): Seq[(String, Int)] =
lines.map { (s, i) => (s, i + offset) }
@@ -201,11 +195,10 @@ private[sbt] object EvaluateConfigurations {
/**
* The name of the class we cast DSL "setting" (vs. definition) lines to.
*/
val SettingsDefinitionName = {
val SettingsDefinitionName =
val _ =
classOf[DslEntry] // this line exists to try to provide a compile-time error when the following line needs to be changed
"sbt.internal.DslEntry"
}
/**
* This actually compiles a scala expression which represents a sbt.internals.DslEntry.
@@ -225,11 +218,11 @@ private[sbt] object EvaluateConfigurations {
imports: Seq[(String, Int)],
expression: String,
range: LineRange
): TrackedEvalResult[DslEntry] = {
): TrackedEvalResult[DslEntry] =
// TODO - Should we try to namespace these between.sbt files? IF they hash to the same value, they may actually be
// exactly the same setting, so perhaps we don't care?
val result =
try {
try
eval.eval(
expression,
imports = new EvalImports(imports.map(_._1)), // name
@@ -237,18 +230,16 @@ private[sbt] object EvaluateConfigurations {
tpeName = Some(SettingsDefinitionName),
line = range.start
)
} catch {
catch
case e: EvalException => throw new MessageOnlyException(e.getMessage)
}
// TODO - keep track of configuration classes defined.
TrackedEvalResult(
result.generated,
loader => {
loader =>
val pos = RangePosition(name, range.shift(1))
result.getValue(loader).asInstanceOf[DslEntry].withPos(pos)
}
)
}
end evaluateDslEntry
/**
* This actually compiles a scala expression which represents a Seq[Setting[_]], although the
@@ -308,13 +299,11 @@ private[sbt] object EvaluateConfigurations {
lines partition { case (_, tree, _) => isDefinition(tree) }
@tailrec
private def isDefinition(tree: Tree): Boolean = {
tree match {
private def isDefinition(tree: Tree): Boolean =
tree match
case Annotated(arg, annot) => isDefinition(arg)
case _: ValOrDefDef => true
case _ => false
}
}
private def extractedValTypes: Seq[String] =
Seq(
@@ -330,7 +319,7 @@ private[sbt] object EvaluateConfigurations {
imports: Seq[(String, Int)],
definitions: Seq[(String, LineRange)],
file: Option[VirtualFileRef],
): EvalDefinitions = {
): EvalDefinitions =
val convertedRanges = definitions.map { (s, r) => (s, r.start to r.end) }
eval.evalDefinitions(
convertedRanges,
@@ -339,13 +328,12 @@ private[sbt] object EvaluateConfigurations {
// file,
extractedValTypes
)
}
}
end EvaluateConfigurations
object BuildUtilLite:
/** Import just the names. */
def importNames(names: Seq[String]): Seq[String] =
if (names.isEmpty) Nil else names.mkString("import ", ", ", "") :: Nil
if names.isEmpty then Nil else names.mkString("import ", ", ", "") :: Nil
/** Prepend `_root_` and import just the names. */
def importNamesRoot(names: Seq[String]): Seq[String] = importNames(names map rootedName)
@@ -353,26 +341,24 @@ object BuildUtilLite:
/** Wildcard import `.{*, given}` for all values. */
def importAll(values: Seq[String]): Seq[String] = importNames(values map { _ + ".{*, given}" })
def importAllRoot(values: Seq[String]): Seq[String] = importAll(values map rootedName)
def rootedName(s: String): String = if (s contains '.') "_root_." + s else s
def rootedName(s: String): String = if s contains '.' then "_root_." + s else s
end BuildUtilLite
object Index {
def allKeys(settings: Seq[Setting[?]]): Set[ScopedKey[?]] = {
object Index:
def allKeys(settings: Seq[Setting[?]]): Set[ScopedKey[?]] =
val result = new java.util.HashSet[ScopedKey[?]]
settings.foreach { s =>
if (!s.key.key.isLocal && result.add(s.key)) {
s.dependencies.foreach(k => if (!k.key.isLocal) result.add(s.key))
}
if !s.key.key.isLocal && result.add(s.key) then
s.dependencies.foreach(k => if !k.key.isLocal then result.add(s.key))
}
result.asScala.toSet
}
def stringToKeyMap(settings: Set[AttributeKey[?]]): Map[String, AttributeKey[?]] =
stringToKeyMap0(settings)(_.label)
private def stringToKeyMap0(
settings: Set[AttributeKey[?]]
)(label: AttributeKey[?] => String): Map[String, AttributeKey[?]] = {
)(label: AttributeKey[?] => String): Map[String, AttributeKey[?]] =
val multiMap = settings.groupBy(label)
val duplicates = multiMap.iterator
.collect { case (k, xs) if xs.size > 1 => (k, xs.map(_.tag)) }
@@ -384,11 +370,10 @@ object Index {
.map { (k, tps) => s"'$k' (${tps.mkString(", ")})" }
.mkString(",")
sys.error(s"Some keys were defined with the same name but different types: $duplicateStr")
}
private type TriggerMap = collection.mutable.HashMap[TaskId[?], Seq[TaskId[?]]]
def triggers(ss: Settings): Triggers = {
def triggers(ss: Settings): Triggers =
val runBefore = new TriggerMap
val triggeredBy = new TriggerMap
ss.values.collect { case base: Task[?] =>
@@ -400,7 +385,10 @@ object Index {
update(triggeredBy, Def.triggeredBy)
}
val onComplete = (GlobalScope / Def.onComplete).get(ss).getOrElse(() => ())
new Triggers(runBefore, triggeredBy, map => { onComplete(); map })
}
}
new Triggers(
runBefore,
triggeredBy,
map =>
onComplete(); map
)
end Index
@@ -26,7 +26,7 @@ private[sbt] final case class LoadedSbtFile(
// rather than what we have now...
definitions: DefinedSbtValues,
generatedFiles: Seq[Path]
) {
):
// We still use merge for now. We track originating sbt file in an alternative manner.
def merge(o: LoadedSbtFile): LoadedSbtFile =
new LoadedSbtFile(
@@ -40,13 +40,13 @@ private[sbt] final case class LoadedSbtFile(
def clearProjects =
new LoadedSbtFile(settings, Nil, importedDefs, manipulations, definitions, generatedFiles)
}
end LoadedSbtFile
/**
* Represents the `val`/`lazy val` definitions defined within a build.sbt file
* which we can reference in other settings.
*/
private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[EvalDefinitions]) {
private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[EvalDefinitions]):
def values(parent: ClassLoader): Seq[Any] =
sbtFiles flatMap (_.values(parent))
@@ -56,15 +56,14 @@ private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[EvalDefinitions]) {
e.loader(cl)
}
def imports: Seq[String] = {
def imports: Seq[String] =
// TODO - Sanity check duplicates and such, so users get a nice warning rather
// than explosion.
for {
for
file <- sbtFiles
m = file.enclosingModule
v <- file.valNames.map(NameTransformer.decode)
} yield s"import ${m}.`${v}`"
}
yield s"import ${m}.`${v}`"
def generated: Seq[Path] =
sbtFiles.flatMap(_.generated)
@@ -79,8 +78,8 @@ private[sbt] final class DefinedSbtValues(val sbtFiles: Seq[EvalDefinitions]) {
*/
def zip(other: DefinedSbtValues): DefinedSbtValues =
new DefinedSbtValues(sbtFiles ++ other.sbtFiles)
}
private[sbt] object DefinedSbtValues {
end DefinedSbtValues
private[sbt] object DefinedSbtValues:
/** Construct a DefinedSbtValues object directly from the underlying representation. */
def apply(eval: EvalDefinitions): DefinedSbtValues =
@@ -89,10 +88,7 @@ private[sbt] object DefinedSbtValues {
/** Construct an empty value object. */
def empty = new DefinedSbtValues(Nil)
}
private[sbt] object LoadedSbtFile {
private[sbt] object LoadedSbtFile:
/** Represents an empty .sbt file: no Projects, imports, or settings. */
def empty = new LoadedSbtFile(Nil, Nil, Nil, Nil, DefinedSbtValues.empty, Nil)
}
@@ -76,7 +76,7 @@ private[sbt] object SbtParser:
* when we know for a fact that the user-provided snippet doesn't
* parse.
*/
private[sbt] class UniqueParserReporter extends Reporter {
private[sbt] class UniqueParserReporter extends Reporter:
private val reporters = new ConcurrentHashMap[String, StoreReporter]()
@@ -91,47 +91,43 @@ private[sbt] object SbtParser:
val reporter = getReporter(sourcePath)
reporter.report(dia)
override def hasErrors: Boolean = {
override def hasErrors: Boolean =
var result = false
reporters.forEachValue(100, r => if (r.hasErrors) result = true)
reporters.forEachValue(100, r => if r.hasErrors then result = true)
result
}
def createReporter(uniqueFileName: String): StoreReporter =
val r = new StoreReporter(null)
reporters.put(uniqueFileName, r)
r
def getOrCreateReporter(uniqueFileName: String): StoreReporter = {
def getOrCreateReporter(uniqueFileName: String): StoreReporter =
val r = reporters.get(uniqueFileName)
if (r == null) createReporter(uniqueFileName)
if r == null then createReporter(uniqueFileName)
else r
}
private def getReporter(fileName: String) = {
private def getReporter(fileName: String) =
val reporter = reporters.get(fileName)
if (reporter == null) {
if reporter == null then
scalacGlobalInitReporter.getOrElse(
sys.error(s"sbt forgot to initialize `scalacGlobalInitReporter`.")
)
} else reporter
}
else reporter
def throwParserErrorsIfAny(reporter: StoreReporter, fileName: String)(using
context: Context
): Unit =
if reporter.hasErrors then {
if reporter.hasErrors then
val seq = reporter.pendingMessages.map { info =>
s"""[$fileName]:${info.pos.line}: ${info.msg}"""
}
val errorMessage = seq.mkString(System.lineSeparator)
val error: String =
if (errorMessage.contains(XML_ERROR))
s"$errorMessage\n${SbtParser.XmlErrorMessage}"
if errorMessage.contains(XML_ERROR) then s"$errorMessage\n${SbtParser.XmlErrorMessage}"
else errorMessage
throw new MessageOnlyException(error)
} else ()
}
else ()
end UniqueParserReporter
private[sbt] var scalacGlobalInitReporter: Option[ConsoleReporter] = None
@@ -251,7 +247,7 @@ private[sbt] case class SbtParser(
private def splitExpressions(
path: VirtualFileRef,
lines: Seq[String]
): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = {
): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) =
// import sbt.internal.parser.MissingBracketHandler.findMissingText
val code = lines.toIndexedSeq.mkString(END_OF_LINE)
val wrapCode = s"""object SyntheticModule {
@@ -301,7 +297,7 @@ private[sbt] case class SbtParser(
(stmt, tree)
}
)
}
end splitExpressions
private def importsToLineRanges(
sourceFile: SourceFile,
@@ -34,7 +34,7 @@ private[sbt] object SbtRefactorings:
def applySessionSettings(
lines: Seq[String],
commands: Seq[SessionSetting]
): Seq[String] = {
): Seq[String] =
val split = SbtParser(FAKE_FILE, lines)
given ctx: Context = SbtParser.defaultGlobalForParser.compileCtx
val recordedCommands = recordCommands(commands, split)
@@ -42,24 +42,21 @@ private[sbt] object SbtRefactorings:
val newContent = replaceFromBottomToTop(lines.mkString(END_OF_LINE), sortedRecordedCommands)
newContent.linesIterator.toList
}
private def replaceFromBottomToTop(
modifiedContent: String,
sortedRecordedCommands: Seq[(Int, String, String)]
) = {
) =
sortedRecordedCommands.foldLeft(modifiedContent) { case (acc, (from, old, replacement)) =>
val before = acc.substring(0, from)
val after = acc.substring(from + old.length, acc.length)
val afterLast = emptyStringForEmptyString(after)
before + replacement + afterLast
}
}
private def emptyStringForEmptyString(text: String) = {
private def emptyStringForEmptyString(text: String) =
val trimmed = text.trim
if (trimmed.isEmpty) trimmed else text
}
if trimmed.isEmpty then trimmed else text
private def recordCommands(commands: Seq[SessionSetting], split: SbtParser)(using Context) =
commands.flatMap { case (_, command) =>
@@ -72,25 +69,22 @@ private[sbt] object SbtRefactorings:
) =
split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { case (acc, (st, tree)) =>
val treeName = extractSettingName(tree)
if (name == treeName) {
if name == treeName then
val replacement =
if (acc.isEmpty) command.mkString(END_OF_LINE)
if acc.isEmpty then command.mkString(END_OF_LINE)
else emptyString
val pos = tree.sourcePos.start - SbtParser.WRAPPER_POSITION_OFFSET
(pos, st, replacement) +: acc
} else {
acc
}
else acc
}
private def toTreeStringMap(command: Seq[String]) = {
private def toTreeStringMap(command: Seq[String]) =
val split = SbtParser(FAKE_FILE, command)
val trees = split.settingsTrees
val seq = trees.map { (statement, tree) =>
(extractSettingName(tree), statement)
}
seq.toMap
}
@tailrec
private def extractSettingName(tree: untpd.Tree): String = tree match
@@ -11,7 +11,7 @@ package parser
abstract class CheckIfParsedSpec(
val splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions
) extends AbstractSpec {
) extends AbstractSpec:
test(s"${this.getClass.getName} should parse sbt file") {
files foreach { (content, description, nonEmptyImports, nonEmptyStatements) =>
@@ -34,13 +34,9 @@ abstract class CheckIfParsedSpec(
private def shouldContains(b: Boolean): String =
s"""Should ${
if (b) {
"contain"
} else {
"not contain"
}
if b then "contain"
else "not contain"
}"""
protected def files: Seq[(String, String, Boolean, Boolean)]
}
end CheckIfParsedSpec
@@ -8,7 +8,7 @@
package sbt.internal.parser
object CommentedXmlSpec extends CheckIfParsedSpec {
object CommentedXmlSpec extends CheckIfParsedSpec:
override protected val files = Seq(
(
@@ -95,4 +95,4 @@ object CommentedXmlSpec extends CheckIfParsedSpec {
false
)
)
}
end CommentedXmlSpec
@@ -10,7 +10,7 @@ package sbt.internal.parser
import sbt.internal.util.MessageOnlyException
object EmbeddedXmlSpec extends CheckIfParsedSpec {
object EmbeddedXmlSpec extends CheckIfParsedSpec:
test("File with xml content should Handle last xml part") {
val errorLine = """<version>4.0<version>"""
@@ -35,17 +35,16 @@ object EmbeddedXmlSpec extends CheckIfParsedSpec {
|
|""".stripMargin
try {
try
splitter(buildSbt)
sys.error("expected MessageOnlyException")
} catch {
catch
case exception: MessageOnlyException =>
val index = buildSbt.linesIterator.indexWhere(line => line.contains(errorLine)) + 1
val numberRegex = """(\d+)""".r
val message = exception.getMessage
val list = numberRegex.findAllIn(message).toList
assert(list.contains(index.toString))
}
}
protected val files = Seq(
@@ -169,5 +168,4 @@ object EmbeddedXmlSpec extends CheckIfParsedSpec {
true
)
)
}
end EmbeddedXmlSpec
@@ -15,7 +15,7 @@ import java.io.File
import scala.io.Source
import sbt.internal.inc.PlainVirtualFileConverter
object NewFormatSpec extends AbstractSpec {
object NewFormatSpec extends AbstractSpec:
val converter = PlainVirtualFileConverter.converter
given splitter: SplitExpressions.SplitExpression = EvaluateConfigurations.splitExpressions
@@ -36,4 +36,4 @@ object NewFormatSpec extends AbstractSpec {
)
}
}
}
end NewFormatSpec
@@ -17,7 +17,7 @@ import java.nio.file.Files
import java.nio.file.Paths
import verify.sourcecode.SourceLocation
object SbtParserErrorSpec extends AbstractSpec {
object SbtParserErrorSpec extends AbstractSpec:
val converter = PlainVirtualFileConverter.converter
@@ -59,4 +59,4 @@ object SbtParserErrorSpec extends AbstractSpec {
callback
throw new AssertionError(s"$pos: expected a MessageOnlyException to be thrown")
catch case ex: MessageOnlyException => ex.getMessage
}
end SbtParserErrorSpec
@@ -17,7 +17,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import scala.jdk.CollectionConverters.*
abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec {
abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec:
private val rootDir = Paths.get(getClass.getResource("/" + folder).toURI)
println(s"Reading files from: $rootDir")
private val converter = PlainVirtualFileConverter.converter
@@ -62,7 +62,7 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec
private def readLines(file: Path): Seq[String] =
Files.readAllLines(file).asScala.toList
}
end AbstractSessionSettingsSpec
object SessionSettingsSpec extends AbstractSessionSettingsSpec("session-settings")
@@ -14,15 +14,15 @@ import sbt.internal.util.LineRange
import xsbti.VirtualFileRef
import dotty.tools.dotc.ast.untpd.Tree
trait SplitExpression {
trait SplitExpression:
extension (splitter: SplitExpressions.SplitExpression)
def apply(s: String): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) =
splitter(VirtualFileRef.of("noFile"), s.split('\n').toSeq)
}
trait SplitExpressionsBehavior extends SplitExpression { this: verify.BasicTestSuite =>
trait SplitExpressionsBehavior extends SplitExpression:
this: verify.BasicTestSuite =>
def newExpressionsSplitter(splitter: SplitExpressions.SplitExpression) = {
def newExpressionsSplitter(splitter: SplitExpressions.SplitExpression) =
test("parse a two settings without intervening blank line") {
val (imports, settings) = splitter(
@@ -64,6 +64,5 @@ trait SplitExpressionsBehavior extends SplitExpression { this: verify.BasicTestS
assert(imports.size == 1)
assert(settingsAndDefs.size == 1)
}
}
}
end newExpressionsSplitter
end SplitExpressionsBehavior
@@ -236,7 +236,7 @@ trait Cont:
val lambda = Lambda(
owner = Symbol.spliceOwner,
tpe = tpe,
rhsFn = (sym, params) => {
rhsFn = (sym, params) =>
val param = params.head.asInstanceOf[Term]
// Called when transforming the tree to add an input.
// For `qual` of type F[A], and a `selection` qual.value,
@@ -261,7 +261,6 @@ trait Cont:
).asTerm
.changeOwner(sym)
case None => modifiedBody.asTerm
}
).asExprOf[a => A1]
val expr = input.term.asExprOf[F[a]]
typed[F[A1]](
@@ -274,6 +273,7 @@ trait Cont:
genMap0[Effect[A]](inner(body).asExprOf[Effect[A]])
case Right(_) =>
flatten(genMap0[F[Effect[A]]](inner(body).asExprOf[F[Effect[A]]]))
end genMap
def genMapN(body: Term, inputs: List[Input]): Expr[F[Effect[A]]] =
def genMapN0[A1: Type](body: Expr[A1]): Expr[F[A1]] =
@@ -285,7 +285,7 @@ trait Cont:
val lambda = Lambda(
owner = Symbol.spliceOwner,
tpe = lambdaTpe,
rhsFn = (sym, params) => {
rhsFn = (sym, params) =>
val p0 = params.head.asInstanceOf[Term]
// Called when transforming the tree to add an input.
// For `qual` of type F[A], and a `selection` qual.value,
@@ -342,9 +342,10 @@ trait Cont:
body = modifiedBody,
input = unitExpr,
).asTerm.changeOwner(sym)
end if
case None =>
modifiedBody.asTerm
}
end match
)
val tupleMapRepr = TypeRepr
.of[Tuple.Map]
@@ -358,11 +359,14 @@ trait Cont:
${ lambda.asExprOf[inputTypeTpe & Tuple => A1] }
)
}
end match
end genMapN0
eitherTree match
case Left(_) =>
genMapN0[Effect[A]](inner(body).asExprOf[Effect[A]])
case Right(_) =>
flatten(genMapN0[F[Effect[A]]](inner(body).asExprOf[F[Effect[A]]]))
end genMapN
// call `ActionCache.cache`
def callActionCache[A1: Type, A2: Type](
@@ -512,6 +516,8 @@ trait Cont:
if hasPrintTreeMacroSetting then Console.err.println(Printer.TreeStructure.show(r.asTerm))
else Console.err.println(r.show)
r
end contImpl
end extension
end Cont
private[sbt] object Cont:
@@ -116,6 +116,8 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
case Inlined(_, _, tree) => extractTags(tree)
case Apply(_, List(arg)) => extractTags(arg)
case _ => extractTags0(tree)
end extractTags
end Input
def cacheLevels(tree: Term): Seq[CacheLevelTag] =
tree.underlying match
@@ -202,6 +204,7 @@ trait ContextUtil[C <: Quotes & scala.Singleton](val valStart: Int):
end scanner
scanner.traverseTree(tree)(Symbol.spliceOwner)
result.toSet
end collectNowarnQuals
def collectDefs(tree: Term, isWrapper: (String, TypeRepr, Term) => Boolean): Set[Symbol] =
val defs = mutable.HashSet[Symbol]()
@@ -70,6 +70,7 @@ trait Convert[C <: Quotes & Singleton] extends ContextUtil[C]:
super.transformTerm(tree)(owner)
end appTransformer
appTransformer.transformTerm(tree)(owner)
end transformWrappers
object Converted:
def success(tree: Term) = Converted.Success(tree, Types.idFun)
@@ -32,18 +32,17 @@ import java.net.MalformedURLException
import org.jline.builtins.InputRC
trait LineReader extends AutoCloseable {
trait LineReader extends AutoCloseable:
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
override def close(): Unit = {}
}
object LineReader {
object LineReader:
val HandleCONT =
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
val MaxHistorySize = 500
private def completer(parser: Parser[?]): Completer = new Completer {
def complete(lr: JLineReader, pl: ParsedLine, candidates: JList[Candidate]): Unit = {
private def completer(parser: Parser[?]): Completer = new Completer:
def complete(lr: JLineReader, pl: ParsedLine, candidates: JList[Candidate]): Unit =
Parser.completions(parser, pl.line(), 10).get.foreach { c =>
/*
* For commands like `~` that delegate parsing to another parser, the `~` may be
@@ -61,39 +60,32 @@ object LineReader {
* the prefix, so that `testOnly com<Tab>` might expand to something like:
* `testOnly testOnly\ com.foo.FooSpec` instead of `testOnly com.foo.FooSpec`.
*/
if (c.append.nonEmpty) {
val cand = pl.line() match {
if c.append.nonEmpty then
val cand = pl.line() match
case line if line.endsWith(" ") => c.append
case line => line.split(" ").last + c.append
}
// https://github.com/jline/jline3/blob/9a4971868e4bdd29a36e454de01f54d3cd6071e0/reader/src/main/java/org/jline/reader/Candidate.java#L123-L131
// "If the candidate is complete and is selected, a space separator will be added."
val complete = false
candidates.add(new Candidate(cand, cand, null, null, null, null, complete))
}
}
}
}
private def inputrcFileUrl(): Option[URL] = {
private def inputrcFileUrl(): Option[URL] =
// keep jline2 compatibility
// https://github.com/jline/jline2/blob/12b98d94589e3bd6a6/src/main/java/jline/console/ConsoleReader.java#L291-L306
sys.props
.get("jline.inputrc")
.flatMap { path =>
try {
Some(url(path))
} catch {
try Some(url(path))
catch
case _: MalformedURLException =>
Some(file(path).toURI.toURL)
}
}
.orElse {
sys.props.get("user.home").map { home =>
val f = file(home) / ".inputrc"
(if (f.isFile) f else file("/etc/inputrc")).toURI.toURL
(if f.isFile then f else file("/etc/inputrc")).toURI.toURL
}
}
}
// cache on memory.
private lazy val inputrcFileContents: Option[Array[Byte]] =
inputrcFileUrl().map(in => sbt.io.IO.readBytes(in.openStream()))
@@ -101,11 +93,11 @@ object LineReader {
historyPath: Option[File],
parser: Parser[?],
terminal: Terminal,
): LineReader = {
): LineReader =
// We may want to consider in-sourcing LineReader.java from jline. We don't otherwise
// directly need jline3 for sbt.
new LineReader {
override def readLine(prompt: String, mask: Option[Char]): Option[String] = {
new LineReader:
override def readLine(prompt: String, mask: Option[Char]): Option[String] =
val term = JLine3(terminal)
val reader = LineReaderBuilder
.builder()
@@ -113,69 +105,62 @@ object LineReader {
.completer(completer(parser))
.option(JLineReader.Option.CASE_INSENSITIVE, true)
.build()
try {
try
inputrcFileContents.foreach { bytes =>
InputRC.configure(
reader,
new ByteArrayInputStream(bytes)
)
}
} catch {
catch
case NonFatal(_) =>
// ignore
}
historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f))
val signalRegistration = terminal match {
val signalRegistration = terminal match
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 {
catch
case e: EndOfFileException =>
if (terminal == Terminal.console && !Terminal.hasConsole) None
if terminal == Terminal.console && !Terminal.hasConsole then None
else Some("exit")
case _: IOError | _: ClosedException => Some("exit")
case _: UserInterruptException | _: ClosedByInterruptException |
_: UncheckedIOException =>
throw new InterruptedException
} finally {
finally
signalRegistration.foreach(_.remove())
terminal.prompt.reset()
term.close()
}
}
}
}
end readLine
def createJLine2Reader(
historyPath: Option[File],
terminal: Terminal,
prompt: Prompt = Prompt.Running,
): ConsoleReader = {
): ConsoleReader =
val cr = Terminal.createReader(terminal, prompt)
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
cr.setBellEnabled(false)
val h = historyPath match {
val h = historyPath match
case None => new MemoryHistory
case Some(file) => new FileHistory(file): MemoryHistory
}
h.setMaxSize(MaxHistorySize)
cr.setHistory(h)
cr.setHistoryEnabled(true)
cr
}
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
def simple(
historyPath: Option[File],
handleCONT: Boolean = HandleCONT,
injectThreadSleep: Boolean = false
): LineReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep)
}
end LineReader
abstract class JLine extends LineReader {
abstract class JLine extends LineReader:
protected def handleCONT: Boolean
protected def reader: ConsoleReader
@@ -188,79 +173,67 @@ abstract class JLine extends LineReader {
}
private def readLineWithHistory(prompt: String, mask: Option[Char]): Option[String] =
reader.getHistory match {
reader.getHistory match
case fh: FileHistory =>
try readLineDirect(prompt, mask)
finally fh.flush()
case _ => readLineDirect(prompt, mask)
}
private def readLineDirect(prompt: String, mask: Option[Char]): Option[String] =
if (handleCONT)
if handleCONT then
Signals.withHandler(() => resume(), signal = Signals.CONT)(() =>
readLineDirectRaw(prompt, mask)
)
else
readLineDirectRaw(prompt, mask)
else readLineDirectRaw(prompt, mask)
private def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = {
private def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] =
val newprompt = handleMultilinePrompt(prompt)
mask match {
mask match
case Some(m) => Option(reader.readLine(newprompt, m))
case None => Option(reader.readLine(newprompt))
}
}
private def handleMultilinePrompt(prompt: String): String = {
private def handleMultilinePrompt(prompt: String): String =
val lines0 = """\r?\n""".r.split(prompt)
lines0.length match {
lines0.length match
case 0 | 1 => handleProgress(prompt)
case _ =>
val lines = lines0.toList map handleProgress
// Workaround for regression jline/jline2#205
reader.getOutput.write(lines.init.mkString("\n") + "\n")
lines.last
}
}
private def handleProgress(prompt: String): String = {
private def handleProgress(prompt: String): String =
import ConsoleAppender.*
if (showProgress) s"$DeleteLine" + prompt
if showProgress then s"$DeleteLine" + prompt
else prompt
}
private def resume(): Unit = {
private def resume(): Unit =
Terminal.reset()
reader.drawLine()
reader.flush()
}
}
end JLine
final class FullReader(
historyPath: Option[File],
complete: Parser[?],
val handleCONT: Boolean,
terminal: Terminal
) extends JLine {
protected val reader: ConsoleReader = {
) extends JLine:
protected val reader: ConsoleReader =
val cr = LineReader.createJLine2Reader(historyPath, terminal)
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr
}
}
class SimpleReader private[sbt] (
historyPath: Option[File],
val handleCONT: Boolean,
terminal: Terminal
) extends JLine {
) extends JLine:
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
this(historyPath, handleCONT, Terminal.console)
protected lazy val reader: ConsoleReader =
LineReader.createJLine2Reader(historyPath, terminal)
}
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false):
def apply(terminal: Terminal): SimpleReader =
new SimpleReader(None, LineReader.HandleCONT, terminal)
}
@@ -13,7 +13,7 @@ package complete
* Represents a set of completions. It exists instead of implicitly defined operations on top of
* Set[Completion] for laziness.
*/
sealed trait Completions {
sealed trait Completions:
def get: Set[Completion]
final infix def x(o: Completions): Completions = flatMap(_ x o)
@@ -30,17 +30,15 @@ sealed trait Completions {
final def map(f: Completion => Completion): Completions = Completions(get map f)
override final def hashCode = get.hashCode
override final def equals(o: Any) = o match {
case c: Completions => get == c.get; case _ => false
}
}
override final def equals(o: Any) = o match
case c: Completions => get == c.get;
case _ => false
object Completions {
object Completions:
/** Returns a lazy Completions instance using the provided Completion Set. */
def apply(cs: => Set[Completion]): Completions = new Completions {
def apply(cs: => Set[Completion]): Completions = new Completions:
lazy val get = cs
}
/** Returns a strict Completions instance using the provided Completion Set. */
def strict(cs: Set[Completion]): Completions = apply(cs)
@@ -59,8 +57,7 @@ object Completions {
/** Returns a strict Completions instance containing only the provided Completion. */
def single(c: Completion): Completions = strict(Set.empty + c)
}
end Completions
/**
* Represents a completion. The abstract members `display` and `append` are best explained with an
@@ -74,7 +71,7 @@ object Completions {
* 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 {
sealed trait Completion:
/**
* The proposed suffix to append to the existing input to complete the last token in the input.
@@ -95,58 +92,51 @@ sealed trait Completion {
else Completions.strict(Set.empty + this)
override final lazy val hashCode = Completion.hashCode(this)
override final def equals(o: Any) = o match {
case c: Completion => Completion.equal(this, c); case _ => false
}
}
override final def equals(o: Any) = o match
case c: Completion => Completion.equal(this, c);
case _ => false
end Completion
final class DisplayOnly(val display: String) extends Completion {
final class DisplayOnly(val display: String) extends Completion:
def isEmpty = display.isEmpty
def append = ""
override def toString = "{" + display + "}"
}
final class Token(val display: String, val append: String) extends Completion {
final class Token(val display: String, val append: String) extends Completion:
def isEmpty = display.isEmpty && append.isEmpty
override final def toString = "[" + display + "]++" + append
}
final class Suggestion(val append: String) extends Completion {
final class Suggestion(val append: String) extends Completion:
def isEmpty = append.isEmpty
def display = append
override def toString = append
}
object Completion {
object Completion:
def concat(a: Completion, b: Completion): Completion =
(a, b) match {
(a, b) match
case (as: Suggestion, bs: Suggestion) => suggestion(as.append + bs.append)
case (at: Token, _) if at.append.isEmpty => b
case _ if a.isEmpty => b
case _ => a
}
def evaluatesRight(a: Completion): Boolean =
a match {
a match
case _: Suggestion => true
case at: Token if at.append.isEmpty => true
case _ => a.isEmpty
}
def equal(a: Completion, b: Completion): Boolean =
(a, b) match {
(a, b) match
case (as: Suggestion, bs: Suggestion) => as.append == bs.append
case (ad: DisplayOnly, bd: DisplayOnly) => ad.display == bd.display
case (at: Token, bt: Token) => at.display == bt.display && at.append == bt.append
case _ => false
}
def hashCode(a: Completion): Int =
a match {
a match
case as: Suggestion => (0, as.append).hashCode
case ad: DisplayOnly => (1, ad.display).hashCode
case at: Token => (2, at.display, at.append).hashCode
}
val empty: Completion = suggestion("")
def single(c: Char): Completion = suggestion(c.toString)
@@ -160,4 +150,4 @@ object Completion {
def tokenDisplay(append: String, display: String): Completion = new Token(display, append)
def suggestion(value: String): Completion = new Suggestion(value)
}
end Completion
@@ -12,7 +12,7 @@ package complete
import java.lang.Character.toLowerCase as lower
/** @author Paul Phillips */
object EditDistance {
object EditDistance:
/**
* Translated from the java version at
@@ -29,23 +29,21 @@ object EditDistance {
matchCost: Int = 0,
caseCost: Int = 1,
transpositions: Boolean = false
): Int = {
): Int =
val _ = transposeCost
val n = s.length
val m = t.length
if (n == 0) {
m
} else if (m == 0) {
n
} else {
if n == 0 then m
else if m == 0 then n
else
val d = Array.ofDim[Int](n + 1, m + 1)
0 to n foreach (x => d(x)(0) = x)
0 to m foreach (x => d(0)(x) = x)
for (i <- 1 to n; s_i = s(i - 1); j <- 1 to m) {
for i <- 1 to n; s_i = s(i - 1); j <- 1 to m do
val t_j = t(j - 1)
val cost =
if (s_i == t_j) matchCost else if (lower(s_i) == lower(t_j)) caseCost else subCost
if s_i == t_j then matchCost else if lower(s_i) == lower(t_j) then caseCost else subCost
val c1 = d(i - 1)(j) + deleteCost
val c2 = d(i)(j - 1) + insertCost
@@ -53,13 +51,11 @@ object EditDistance {
d(i)(j) = c1 min c2 min c3
if (transpositions) {
if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1))
if transpositions then
if i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1) then
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost)
}
}
d(n)(m)
}
}
}
end if
end levenshtein
end EditDistance
@@ -17,7 +17,7 @@ import sbt.io.IO
* 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 {
trait ExampleSource:
/**
* @return
@@ -35,14 +35,12 @@ trait ExampleSource {
*/
def withAddedPrefix(addedPrefix: String): 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.
*/
sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSource {
sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSource:
override def withAddedPrefix(addedPrefix: String): ExampleSource =
FixedSetExamples(examplesWithRemovedPrefix(addedPrefix))
@@ -51,7 +49,6 @@ sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSo
private def examplesWithRemovedPrefix(prefix: String) = examples.collect {
case example if example.startsWith(prefix) => example.substring(prefix.length)
}
}
/**
* Provides path completion examples based on files in the base directory.
@@ -60,13 +57,13 @@ sealed case class FixedSetExamples(examples: Iterable[String]) extends ExampleSo
* @param prefix
* the part of the path already written by the user.
*/
class FileExamples(base: File, prefix: String = "") extends ExampleSource {
class FileExamples(base: File, prefix: String = "") extends ExampleSource:
override def apply(): LazyList[String] = files(base).map(_.substring(prefix.length))
override def withAddedPrefix(addedPrefix: String): FileExamples =
new FileExamples(base, prefix + addedPrefix)
protected def files(directory: File): LazyList[String] = {
protected def files(directory: File): LazyList[String] =
val childPaths = LazyList(IO.listFiles(directory)*)
val prefixedDirectChildPaths = childPaths
.map(IO.relativize(base, _).get)
@@ -76,8 +73,6 @@ class FileExamples(base: File, prefix: String = "") extends ExampleSource {
.map(IO.relativize(base, _).get)
.filter(dirStartsWithPrefix)
prefixedDirectChildPaths ++ dirsToRecurseInto.flatMap(dir => files(new File(base, dir)))
}
private def dirStartsWithPrefix(relativizedPath: String): Boolean =
relativizedPath.startsWith(prefix) || prefix.startsWith(relativizedPath)
}
@@ -12,7 +12,7 @@ package complete
import History.number
import java.io.File
final class History private (val lines: IndexedSeq[String], val path: Option[File]) {
final class History private (val lines: IndexedSeq[String], val path: Option[File]):
private def reversed = lines.reverse
def all: Seq[String] = lines
@@ -20,28 +20,23 @@ final class History private (val lines: IndexedSeq[String], val path: Option[Fil
def !! : Option[String] = !-(1)
def apply(i: Int): Option[String] =
if (0 <= i && i < size) Some(lines(i))
else {
sys.error("Invalid history index: " + i)
}
if 0 <= i && i < size then Some(lines(i))
else sys.error("Invalid history index: " + i)
def !(i: Int): Option[String] = apply(i)
def !(s: String): Option[String] =
number(s) match {
case Some(n) => if (n < 0) !-(-n) else apply(n)
number(s) match
case Some(n) => if n < 0 then !-(-n) else apply(n)
case None => nonEmpty(s) { reversed.find(_.startsWith(s)) }
}
def !-(n: Int): Option[String] = apply(size - n - 1)
def !?(s: String): Option[String] = nonEmpty(s) { reversed.drop(1).find(_.contains(s)) }
private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] =
if (s.isEmpty)
sys.error("No action specified to history command")
else
act
if s.isEmpty then sys.error("No action specified to history command")
else act
def list(historySize: Int, show: Int): Seq[String] =
lines.toList
@@ -49,13 +44,12 @@ final class History private (val lines: IndexedSeq[String], val path: Option[Fil
.zipWithIndex
.map { (line, number) => " " + number + " " + line }
.takeRight(show max 1)
}
end History
object History {
object History:
def apply(lines: Seq[String], path: Option[File], error: String => Unit): History =
new History(lines.toIndexedSeq, path)
def apply(lines: Seq[String], path: Option[File]): History =
new History(lines.toIndexedSeq, path)
def number(s: String): Option[Int] = s.toIntOption
}
@@ -12,7 +12,7 @@ package complete
import sbt.io.IO
import Util.*
object HistoryCommands {
object HistoryCommands:
val Start = "!"
// second characters
val Contains = "?"
@@ -59,20 +59,22 @@ object HistoryCommands {
lazy val last = Last ^^^ { execute(_.!!) }
lazy val list = ListCommands ~> (num ?? Int.MaxValue) map { show => (h: History) =>
{ printHistory(h, MaxLines, show); nil[String].some }
printHistory(h, MaxLines, show); nil[String].some
}
lazy val execStr = flag('?') ~ token(any.+.string, "<string>") map { (contains, str) =>
execute(h => if (contains) h !? str else h ! str)
execute(h => if contains then h !? str else h ! str)
}
lazy val execInt = flag('-') ~ num map { (neg, value) =>
execute(h => if (neg) h !- value else h ! value)
execute(h => if neg then h !- value else h ! value)
}
lazy val help = success((h: History) => { printHelp(); nil[String].some })
lazy val help = success((h: History) =>
printHelp(); nil[String].some
)
def execute(f: History => Option[String]): History => Option[List[String]] = (h: History) => {
def execute(f: History => Option[String]): History => Option[List[String]] = (h: History) =>
val command = f(h).filterNot(_.startsWith(Start))
val lines = h.lines.toArray
command.foreach(lines(lines.length - 1) = _)
@@ -80,8 +82,7 @@ object HistoryCommands {
IO.writeLines(h, lines.toSeq)
}
command.toList.some
}
val actionParser: Parser[complete.History => Option[List[String]]] =
Start ~> (help | last | execInt | list | execStr) // execStr must come last
}
end HistoryCommands
@@ -14,7 +14,7 @@ import jline.console.completer.{ Completer, CompletionHandler }
import scala.annotation.tailrec
import scala.jdk.CollectionConverters.*
object JLineCompletion {
object JLineCompletion:
def installCustomCompletor(reader: ConsoleReader, parser: Parser[?]): Unit =
installCustomCompletor(reader)(parserAsCompletor(parser))
@@ -26,14 +26,13 @@ object JLineCompletion {
def installCustomCompletor(
complete: (ConsoleReader, Int) => Boolean,
reader: ConsoleReader
): Unit = {
): Unit =
reader.removeCompleter(DummyCompletor)
reader.addCompleter(DummyCompletor)
reader.setCompletionHandler(new CustomHandler(complete))
}
private final class CustomHandler(completeImpl: (ConsoleReader, Int) => Boolean)
extends CompletionHandler {
extends CompletionHandler:
private var previous: Option[(String, Int)] = None
private var level: Int = 1
@@ -41,144 +40,120 @@ object JLineCompletion {
reader: ConsoleReader,
candidates: java.util.List[CharSequence],
position: Int
) = {
) =
val current = Some(bufferSnapshot(reader))
level = if (current == previous) level + 1 else 1
level = if current == previous then level + 1 else 1
previous = current
try completeImpl(reader, level)
catch {
catch
case e: Exception =>
reader.print("\nException occurred while determining completions.")
e.printStackTrace()
false
}
}
}
// always provides dummy completions so that the custom completion handler gets called
// (ConsoleReader doesn't call the handler if there aren't any completions)
// the custom handler will then throw away the candidates and call the custom function
private object DummyCompletor extends Completer {
private object DummyCompletor extends Completer:
override def complete(
buffer: String,
cursor: Int,
candidates: java.util.List[CharSequence]
): Int = {
): Int =
candidates.add("dummy")
0
}
}
def parserAsCompletor(p: Parser[?]): (String, Int) => (Seq[String], Seq[String]) =
(str, level) => convertCompletions(Parser.completions(p, str, level))
def convertCompletions(c: Completions): (Seq[String], Seq[String]) = {
def convertCompletions(c: Completions): (Seq[String], Seq[String]) =
val cs = c.get
if (cs.isEmpty)
(Nil, "{invalid input}" :: Nil)
else
convertCompletions(cs)
}
if cs.isEmpty then (Nil, "{invalid input}" :: Nil)
else convertCompletions(cs)
def convertCompletions(cs: Set[Completion]): (Seq[String], Seq[String]) = {
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
if comp.isEmpty then t
else (appendNonEmpty(insert, comp.append), appendNonEmpty(display, comp.display))
}
(insert.toSeq, display.toSeq.sorted)
}
def appendNonEmpty(set: Set[String], add: String) = if (add.trim.isEmpty) set else set + add
def appendNonEmpty(set: Set[String], add: String) = if add.trim.isEmpty then set else set + add
def customCompletor(
f: (String, Int) => (Seq[String], Seq[String])
): (ConsoleReader, Int) => Boolean =
(reader, level) => {
(reader, level) =>
val success = complete(beforeCursor(reader), string => f(string, level), reader)
reader.flush()
success
}
def bufferSnapshot(reader: ConsoleReader): (String, Int) = {
def bufferSnapshot(reader: ConsoleReader): (String, Int) =
val b = reader.getCursorBuffer
(b.buffer.toString, b.cursor)
}
def beforeCursor(reader: ConsoleReader): String = {
def beforeCursor(reader: ConsoleReader): String =
val b = reader.getCursorBuffer
b.buffer.substring(0, b.cursor)
}
// returns false if there was nothing to insert and nothing to display
def complete(
beforeCursor: String,
completions: String => (Seq[String], Seq[String]),
reader: ConsoleReader
): Boolean = {
): Boolean =
val (insert, display) = completions(beforeCursor)
val common = commonPrefix(insert)
if (common.isEmpty)
if (display.isEmpty)
()
else
showCompletions(display, reader)
else
appendCompletion(common, reader)
if common.isEmpty then
if display.isEmpty then ()
else showCompletions(display, reader)
else appendCompletion(common, reader)
!(common.isEmpty && display.isEmpty)
}
def appendCompletion(common: String, reader: ConsoleReader): Unit = {
def appendCompletion(common: String, reader: ConsoleReader): Unit =
reader.getCursorBuffer.write(common)
reader.redrawLine()
}
/**
* `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 = {
def showCompletions(display: Seq[String], reader: ConsoleReader): Unit =
printCompletions(display, reader)
reader.drawLine()
}
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit = {
def printCompletions(cs: Seq[String], reader: ConsoleReader): Unit =
val print = shouldPrint(cs, reader)
reader.println()
if (print) printLinesAndColumns(cs, reader)
}
if print then printLinesAndColumns(cs, reader)
def printLinesAndColumns(cs: Seq[String], reader: ConsoleReader): Unit = {
def printLinesAndColumns(cs: Seq[String], reader: ConsoleReader): Unit =
val (lines, columns) = cs partition hasNewline
for (line <- lines) {
for line <- lines do
reader.print(line)
if (line.charAt(line.length - 1) != '\n')
reader.println()
}
if line.charAt(line.length - 1) != '\n' then reader.println()
reader.printColumns(columns.map(_.trim).asJava)
}
def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0
def shouldPrint(cs: Seq[String], reader: ConsoleReader): Boolean = {
def shouldPrint(cs: Seq[String], reader: ConsoleReader): Boolean =
val size = cs.size
(size <= reader.getAutoprintThreshold) ||
confirm("Display all %d possibilities? (y or n) ".format(size), 'y', 'n', reader)
}
def confirm(prompt: String, trueC: Char, falseC: Char, reader: ConsoleReader): Boolean = {
def confirm(prompt: String, trueC: Char, falseC: Char, reader: ConsoleReader): Boolean =
reader.println()
reader.print(prompt)
reader.flush()
reader.readCharacter(trueC, falseC) == trueC
}
def commonPrefix(s: Seq[String]): String = if (s.isEmpty) "" else s reduceLeft commonPrefix
def commonPrefix(s: Seq[String]): String = if s.isEmpty then "" else s reduceLeft commonPrefix
def commonPrefix(a: String, b: String): String = {
def commonPrefix(a: String, b: String): String =
val len = scala.math.min(a.length, b.length)
@tailrec def loop(i: Int): Int = if (i >= len) len else if (a(i) != b(i)) i else loop(i + 1)
@tailrec def loop(i: Int): Int =
if i >= len then len else if a(i) != b(i) then i else loop(i + 1)
a.substring(0, loop(0))
}
}
end JLineCompletion
@@ -31,7 +31,7 @@ trait Parser[+A1]:
def valid: Boolean
end Parser
sealed trait RichParser[A] {
sealed trait RichParser[A]:
/**
* Apply the original Parser and then apply `next` (in order). The result of both is provides as a
@@ -153,11 +153,11 @@ sealed trait RichParser[A] {
* parser and uses its result for the overall result.
*/
def flatMap[B](f: A => Parser[B]): Parser[B]
}
end RichParser
/** Contains Parser implementation helper methods not typically needed for using parsers. */
object Parser extends ParserMain:
sealed abstract class Result[+T] {
sealed abstract class Result[+T]:
def isFailure: Boolean
def isValid: Boolean
def errors: Seq[String]
@@ -170,44 +170,42 @@ object Parser extends ParserMain:
def seq[B](b: => Result[B]): Result[(T, B)] = app(b)((m, n) => (m, n))
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C]
def toEither: Either[() => Seq[String], T]
}
final case class Value[+T](value: T) extends Result[T] {
final case class Value[+T](value: T) extends Result[T]:
def isFailure = false
def isValid: Boolean = true
def errors = Nil
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C] = b match {
def app[B, C](b: => Result[B])(f: (T, B) => C): Result[C] = b match
case fail: Failure => fail
case Value(bv) => Value(f(value, bv))
}
infix def &&(b: => Result[?]): Result[T] = b match { case f: Failure => f; case _ => this }
infix def &&(b: => Result[?]): Result[T] = b match
case f: Failure => f;
case _ => this
infix def or[B >: T](b: => Result[B]): Result[B] = this
def either[B](b: => Result[B]): Result[Either[T, B]] = Value(Left(value))
def map[B](f: T => B): Result[B] = Value(f(value))
def flatMap[B](f: T => Result[B]): Result[B] = f(value)
def filter(f: T => Boolean, msg: => String): Result[T] = if (f(value)) this else mkFailure(msg)
def filter(f: T => Boolean, msg: => String): Result[T] =
if f(value) then this else mkFailure(msg)
def toEither = Right(value)
}
final class Failure private[sbt] (mkErrors: => Seq[String], val definitive: Boolean)
extends Result[Nothing] {
extends Result[Nothing]:
lazy val errors: Seq[String] = mkErrors
def isFailure = true
def isValid = false
def map[B](f: Nothing => B) = this
def flatMap[B](f: Nothing => Result[B]) = this
infix def or[B](b: => Result[B]): Result[B] = b match {
infix def or[B](b: => Result[B]): Result[B] = b match
case v: Value[B] => v
case f: Failure => if (definitive) this else this ++ f
}
case f: Failure => if definitive then this else this ++ f
def either[B](b: => Result[B]): Result[Either[Nothing, B]] = b match {
def either[B](b: => Result[B]): Result[Either[Nothing, B]] = b match
case Value(v) => Value(Right(v))
case f: Failure => if (definitive) this else this ++ f
}
case f: Failure => if definitive then this else this ++ f
def filter(f: Nothing => Boolean, msg: => String) = this
def app[B, C](b: => Result[B])(f: (Nothing, B) => C): Result[C] = this
@@ -215,7 +213,7 @@ object Parser extends ParserMain:
def toEither = Left(() => errors)
private[sbt] def ++(f: Failure) = mkFailures(errors ++ f.errors)
}
end Failure
def mkFailures(errors: => Seq[String], definitive: Boolean = false): Failure =
new Failure(errors.distinct, definitive)
@@ -224,26 +222,25 @@ object Parser extends ParserMain:
new Failure(error :: Nil, definitive)
def tuple[A, B](a: Option[A], b: Option[B]): Option[(A, B)] =
(a, b) match { case (Some(av), Some(bv)) => Some((av, bv)); case _ => None }
(a, b) match
case (Some(av), Some(bv)) => Some((av, bv));
case _ => None
def mapParser[A, B](a: Parser[A], f: A => B): Parser[B] =
a.ifValid {
a.result match {
a.result match
case Some(av) => success(f(av))
case None =>
a match {
a match
case m: MapParser[?, ?] => m.map(f)
case _ => new MapParser(a, f)
}
}
}
def bindParser[A, B](a: Parser[A], f: A => Parser[B]): Parser[B] =
a.ifValid {
a.result match {
a.result match
case Some(av) => f(av)
case None => new BindParser(a, f)
}
}
def filterParser[T](
@@ -253,21 +250,19 @@ object Parser extends ParserMain:
msg: String => String
): Parser[T] =
a.ifValid {
a.result match {
a.result match
case Some(av) if f(av) => success(av)
case _ => new Filter(a, f, seen, msg)
}
}
def seqParser[A, B](a: Parser[A], b: Parser[B]): Parser[(A, B)] =
a.ifValid {
b.ifValid {
(a.result, b.result) match {
(a.result, b.result) match
case (Some(av), Some(bv)) => success((av, bv))
case (Some(av), None) => b.map(bv => (av, bv))
case (None, Some(bv)) => a.map(av => (av, bv))
case (None, None) => new SeqParser(a, b)
}
}
}
@@ -300,7 +295,7 @@ object Parser extends ParserMain:
min: Int,
max: UpperBound,
revAcc: List[T]
): Parser[Seq[T]] = {
): Parser[Seq[T]] =
assume(min >= 0, "Minimum must be greater than or equal to zero (was " + min + ")")
assume(
max >= min,
@@ -308,39 +303,35 @@ object Parser extends ParserMain:
)
def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] =
repeated match {
repeated match
case _: Invalid if min == 0 => invalidButOptional
case i: Invalid => i
case _ =>
repeated.result match {
repeated.result match
case Some(value) =>
success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
case None =>
if (max.isZero) success(revAcc.reverse)
if max.isZero then success(revAcc.reverse)
else new Repeat(partial, repeated, min, max, revAcc)
}
}
partial match {
partial match
case Some(part) =>
part.ifValid {
part.result match {
part.result match
case Some(value) => repeat(None, repeated, min, max, value :: revAcc)
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
}
}
case None => checkRepeated(success(Nil))
}
}
end repeat
def and[T](a: Parser[T], b: Parser[?]): Parser[T] = a.ifValid(b.ifValid(new And(a, b)))
end Parser
trait ParserMain {
trait ParserMain:
/** Provides combinators for Parsers. */
implicit def richParser[A](a: Parser[A]): RichParser[A] = new RichParser[A] {
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)
def |[B >: A](b: Parser[B]) = homParser[B](a, b)
@@ -375,7 +366,6 @@ trait ParserMain {
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
def string(using ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
}
implicit def literalRichCharParser(c: Char): RichParser[Char] = richParser(c)
implicit def literalRichStringParser(s: String): RichParser[String] = richParser(s)
@@ -405,21 +395,19 @@ trait ParserMain {
invalid(msg :: Nil, definitive)
/** Defines a parser that always succeeds on empty input with the result `value`. */
def success[T](value: T): Parser[T] = new ValidParser[T] {
def success[T](value: T): Parser[T] = new ValidParser[T]:
override def result = Some(value)
def resultEmpty = Value(value)
def derive(c: Char) = Parser.failure("Expected end of input.")
def completions(level: Int) = Completions.empty
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.
*/
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] = {
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.
@@ -428,10 +416,9 @@ trait ParserMain {
charClass(r contains _, label).examples(r.map(_.toString)*)
/** Defines a Parser that parses a single character only if it is contained in `legal`. */
def chars(legal: String): Parser[Char] = {
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
@@ -441,14 +428,13 @@ trait ParserMain {
new CharacterClass(f, label)
/** Presents a single Char `ch` as a Parser that only parses that exact character. */
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char]:
def result = None
private lazy val fail = mkFailure("Expected '" + ch + "'")
def resultEmpty = fail
def derive(c: Char) = if (c == ch) success(ch) else new Invalid(fail)
def derive(c: Char) = if c == ch then success(ch) else new Invalid(fail)
def completions(level: Int) = Completions.single(Completion.suggestion(ch.toString))
override def toString = "'" + ch + "'"
}
/**
* Presents a literal String `s` as a Parser that only parses that exact text and provides it as
@@ -457,7 +443,7 @@ trait ParserMain {
implicit def literal(s: String): Parser[String] = stringLiteral(s, 0)
/** See [[unapply]]. */
object ~ {
object `~`:
/** Convenience for destructuring a tuple that mirrors the `~` combinator. */
def unapply[A, B](t: (A, B)): (A, B) = t
@@ -469,8 +455,6 @@ trait ParserMain {
@deprecated("Use unapply instead", "2.0.0")
private[sbt] def unapplyDeprecated[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`.
@@ -490,7 +474,7 @@ trait ParserMain {
* See also [[sampleParse]] and [[sampleCompletions]].
*/
def sample(str: String, parser: Parser[?], completions: Boolean = false): Unit =
if (completions) sampleCompletions(str, parser) else sampleParse(str, parser)
if completions then sampleCompletions(str, parser) else sampleParse(str, parser)
/**
* Convenience method to use when developing a parser. `parser` is applied to the input `str` and
@@ -498,10 +482,9 @@ trait ParserMain {
* error message is displayed.
*/
def sampleParse(str: String, parser: Parser[?]): Unit =
parse(str, parser) match {
parse(str, parser) match
case Left(msg) => println(msg)
case Right(v) => println(v)
}
/**
* Convenience method to use when developing a parser. `parser` is applied to the input `str` and
@@ -512,24 +495,21 @@ trait ParserMain {
Parser.completions(parser, str, level).get foreach println
// intended to be temporary pending proper error feedback
def result[T](p: Parser[T], s: String): Either[() => (Seq[String], Int), T] = {
def result[T](p: Parser[T], s: String): Either[() => (Seq[String], Int), T] =
@tailrec
def loop(i: Int, a: Parser[T]): Either[() => (Seq[String], Int), T] =
a match {
a match
case Invalid(f) => Left(() => (f.errors, i))
case _ =>
val ci = i + 1
if (ci >= s.length)
if ci >= s.length then
a.resultEmpty.toEither.left.map { msgs0 => () =>
val msgs = msgs0()
val nonEmpty = if (msgs.isEmpty) Seq("Unexpected end of input") else msgs
val nonEmpty = if msgs.isEmpty then Seq("Unexpected end of input") else msgs
(nonEmpty, ci)
}
else
loop(ci, a.derive(s((ci))))
}
else loop(ci, a.derive(s((ci))))
loop(-1, p)
}
/** Applies parser `p` to input `s`. */
def apply[T](p: Parser[T])(s: String): Parser[T] =
@@ -537,7 +517,7 @@ trait ParserMain {
/** Applies parser `p` to a single character of input. */
def derive1[T](p: Parser[T], c: Char): Parser[T] =
if (p.valid) p.derive(c) else p
if p.valid then p.derive(c) else p
/**
* Applies parser `p` to input `s` and returns the completions at verbosity `level`. The
@@ -575,27 +555,23 @@ trait ParserMain {
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
): Parser[A] =
if (a.valid) {
a.result match {
if a.valid then
a.result match
case Some(av) => success(av)
case None =>
new ParserWithExamples(a, completions, maxNumberOfExamples, removeInvalidExamples)
}
} else a
else a
def matched(
t: Parser[?],
seen: Vector[Char] = Vector.empty,
partial: Boolean = false
): Parser[String] =
t match {
case i: Invalid => if (partial && seen.nonEmpty) success(seen.mkString) else i
t match
case i: Invalid => if partial && seen.nonEmpty then success(seen.mkString) else i
case _ =>
if (t.result.isEmpty)
new MatchedString(t, seen, partial)
else
success(seen.mkString)
}
if t.result.isEmpty then new MatchedString(t, seen, partial)
else success(seen.mkString)
/**
* Establishes delegate parser `t` as a single token of tab completion. When tab completion of
@@ -633,50 +609,44 @@ trait ParserMain {
mkToken(t, "", complete)
private[sbt] def mkToken[T](t: Parser[T], seen: String, complete: TokenCompletions): Parser[T] =
if (t.valid && !t.isTokenStart)
if (t.result.isEmpty) new TokenStart(t, seen, complete) else t
else
t
if t.valid && !t.isTokenStart then
if t.result.isEmpty then new TokenStart(t, seen, complete) else t
else t
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] = (a, b) match {
def homParser[A](a: Parser[A], b: Parser[A]): Parser[A] = (a, b) match
case (Invalid(af), Invalid(bf)) => Invalid(af ++ bf)
case (Invalid(_), bv) => bv
case (av, Invalid(_)) => av
case (_, _) => new HomParser(a, b)
}
def not(p: Parser[?], failMessage: String): Parser[Unit] = p.result match {
def not(p: Parser[?], failMessage: String): Parser[Unit] = p.result match
case None => new Not(p, failMessage)
case Some(_) => failure(failMessage)
}
def oneOf[T](p: Seq[Parser[T]]): Parser[T] = p.reduceLeft(_ | _)
def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = seq0(p, Nil)
def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] = {
def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] =
val (newErrors, valid) = separate(p) {
case Invalid(f) => Left(() => f.errors): Either[() => Seq[String], Parser[T]]
case ok => Right(ok): Either[() => Seq[String], Parser[T]]
}
def combinedErrors = errors ++ newErrors.flatMap(_())
if (valid.isEmpty) invalid(combinedErrors) else new ParserSeq(valid, combinedErrors)
}
if valid.isEmpty then invalid(combinedErrors) else new ParserSeq(valid, combinedErrors)
def stringLiteral(s: String, start: Int): Parser[String] = {
def stringLiteral(s: String, start: Int): Parser[String] =
val len = s.length
if (len == 0) sys.error("String literal cannot be empty")
else if (start >= len) success(s)
if len == 0 then sys.error("String literal cannot be empty")
else if start >= len then success(s)
else new StringLiteral(s, start)
}
}
end ParserMain
sealed trait ValidParser[T] extends Parser[T] {
sealed trait ValidParser[T] extends Parser[T]:
final def valid = true
final def failure = None
final def ifValid[S](p: => Parser[S]): Parser[S] = p
}
private final case class Invalid(fail: Failure) extends Parser[Nothing] {
private final case class Invalid(fail: Failure) extends Parser[Nothing]:
def failure = Some(fail)
def result = None
def resultEmpty = fail
@@ -685,138 +655,114 @@ private final case class Invalid(fail: Failure) extends Parser[Nothing] {
override def toString = fail.errors.mkString("; ")
def valid = false
def ifValid[S](p: => Parser[S]): Parser[S] = this
}
private final case class SoftInvalid(fail: Failure) extends ValidParser[Nothing] {
private final case class SoftInvalid(fail: Failure) extends ValidParser[Nothing]:
def result = None
def resultEmpty = fail
def derive(c: Char) = Invalid(fail)
def completions(level: Int) = Completions.nil
override def toString = fail.errors.mkString("; ")
}
private final class TrapAndFail[A](a: Parser[A]) extends ValidParser[A] {
private final class TrapAndFail[A](a: Parser[A]) extends ValidParser[A]:
def result =
try {
a.result
} catch { case _: Exception => None }
try a.result
catch case _: Exception => None
def resultEmpty =
try {
a.resultEmpty
} catch { case e: Exception => fail(e) }
try a.resultEmpty
catch case e: Exception => fail(e)
def derive(c: Char) =
try {
trapAndFail(a.derive(c))
} catch {
case e: Exception => Invalid(fail(e))
}
try trapAndFail(a.derive(c))
catch case e: Exception => Invalid(fail(e))
def completions(level: Int) =
try {
a.completions(level)
} catch {
case _: Exception => Completions.nil
}
try a.completions(level)
catch case _: Exception => Completions.nil
override def toString = "trap(" + a + ")"
override def isTokenStart = a.isTokenStart
private def fail(e: Exception): Failure = mkFailure(e.toString)
}
private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A] {
private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A]:
def result = a.result
def resultEmpty = a.resultEmpty match {
case _: Failure => mkFailure(message); case v: Value[A] => v
}
def resultEmpty = a.resultEmpty match
case _: Failure => mkFailure(message);
case v: Value[A] => v
def derive(c: Char) = onFailure(a.derive(c), message)
def completions(level: Int) = a.completions(level)
override def toString = "(" + a + " !!! \"" + message + "\" )"
override def isTokenStart = a.isTokenStart
}
private final class SeqParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[(A, B)] {
private final class SeqParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[(A, B)]:
lazy val result = tuple(a.result, b.result)
lazy val resultEmpty = a.resultEmpty.seq(b.resultEmpty)
def derive(c: Char) = {
def derive(c: Char) =
val common = a.derive(c) ~ b
a.resultEmpty match {
a.resultEmpty match
case Value(av) => common | b.derive(c).map(br => (av, br))
case _: Failure => common
}
}
def completions(level: Int) = a.completions(level).x(b.completions(level))
override def toString = "(" + a + " ~ " + b + ")"
}
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A] {
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A]:
lazy val result = tuple(a.result, b.result) map (_._1)
def derive(c: Char) = a.derive(c) | b.derive(c)
lazy val resultEmpty = a.resultEmpty or b.resultEmpty
def completions(level: Int) = a.completions(level) ++ b.completions(level)
override def toString = "(" + a + " | " + b + ")"
}
private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A, B]] {
private final class HetParser[A, B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A, B]]:
lazy val result = tuple(a.result, b.result) map { case (a, _) => Left(a) }
def derive(c: Char) = a.derive(c) || b.derive(c)
lazy val resultEmpty = a.resultEmpty.either(b.resultEmpty)
def completions(level: Int) = a.completions(level) ++ b.completions(level)
override def toString = "(" + a + " || " + b + ")"
}
private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String])
extends ValidParser[Seq[T]] {
extends ValidParser[Seq[T]]:
assert(a.nonEmpty)
lazy val resultEmpty: Result[Seq[T]] = {
lazy val resultEmpty: Result[Seq[T]] =
val res = a.map(_.resultEmpty)
val (failures, values) = separate(res)(_.toEither)
// if(failures.isEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
if (values.nonEmpty) Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
}
if values.nonEmpty then Value(values) else mkFailures(failures.flatMap(_()) ++ errors)
def result = {
def result =
val success = a.flatMap(_.result)
if (success.length == a.length) Some(success) else None
}
if success.length == a.length then Some(success) else None
def completions(level: Int) = a.map(_.completions(level)).reduceLeft(_ ++ _)
def derive(c: Char) = seq0(a.map(_.derive(c)), errors)
override def toString = "seq(" + a + ")"
}
private final class BindParser[A, B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B] {
private final class BindParser[A, B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B]:
lazy val result = a.result flatMap (av => f(av).result)
lazy val resultEmpty = a.resultEmpty.flatMap(av => f(av).resultEmpty)
def completions(level: Int) =
a.completions(level) flatMap { c =>
apply(a)(c.append).resultEmpty match {
apply(a)(c.append).resultEmpty match
case _: Failure => Completions.strict(Set.empty + c)
case Value(av) => c.x(f(av).completions(level))
}
}
def derive(c: Char) = {
def derive(c: Char) =
val common = a.derive(c).flatMap(f)
a.resultEmpty match {
a.resultEmpty match
case Value(av) => common | derive1(f(av), c)
case _: Failure => common
}
}
override def isTokenStart = a.isTokenStart
override def toString = "bind(" + a + ")"
}
private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser[B] {
private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser[B]:
lazy val result = a.result map f
lazy val resultEmpty = a.resultEmpty.map(f)
def derive(c: Char) = a.derive(c).map(f)
@@ -824,10 +770,9 @@ private final class MapParser[A, B](a: Parser[A], f: A => B) extends ValidParser
override def isTokenStart = a.isTokenStart
override def toString = "map(" + a + ")"
def map[C](g: B => C) = new MapParser[A, C](a, f.andThen(g))
}
private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String)
extends ValidParser[T] {
extends ValidParser[T]:
def filterResult(r: Result[T]) = r.filter(f, msg(seen))
lazy val result = p.result filter f
lazy val resultEmpty = filterResult(p.resultEmpty)
@@ -839,59 +784,52 @@ private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg:
override def toString = "filter(" + p + ")"
override def isTokenStart = p.isTokenStart
}
private final class MatchedString(delegate: Parser[?], seenV: Vector[Char], partial: Boolean)
extends ValidParser[String] {
extends ValidParser[String]:
lazy val seen = seenV.mkString
def derive(c: Char) = matched(delegate.derive(c), seenV :+ c, partial)
def completions(level: Int) = delegate.completions(level)
def result = if (delegate.result.isDefined) Some(seen) else None
def result = if delegate.result.isDefined then Some(seen) else None
def resultEmpty = delegate.resultEmpty match {
case f: Failure if !partial => f; case _ => Value(seen)
}
def resultEmpty = delegate.resultEmpty match
case f: Failure if !partial => f;
case _ => Value(seen)
override def isTokenStart = delegate.isTokenStart
override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")"
}
private final class TokenStart[T](delegate: Parser[T], seen: String, complete: TokenCompletions)
extends ValidParser[T] {
extends ValidParser[T]:
def derive(c: Char) = mkToken(delegate.derive(c), seen + c, complete)
def completions(level: Int) = complete match {
def completions(level: Int) = complete match
case dc: TokenCompletions.Delegating =>
dc.completions(seen, level, delegate.completions(level))
case fc: TokenCompletions.Fixed => fc.completions(seen, level)
}
def result = delegate.result
def resultEmpty = delegate.resultEmpty
override def isTokenStart = true
override def toString = "token('" + complete + ", " + delegate + ")"
}
private final class And[T](a: Parser[T], b: Parser[?]) extends ValidParser[T] {
private final class And[T](a: Parser[T], b: Parser[?]) extends ValidParser[T]:
lazy val result = tuple(a.result, b.result) map { _._1 }
def derive(c: Char) = a.derive(c) & b.derive(c)
def completions(level: Int) = a.completions(level).filterS(s => apply(b)(s).resultEmpty.isValid)
lazy val resultEmpty = a.resultEmpty && b.resultEmpty
override def toString = s"(${a}) && (${b})"
}
private final class Not(delegate: Parser[?], failMessage: String) extends ValidParser[Unit] {
def derive(c: Char) = if (delegate.valid) not(delegate.derive(c), failMessage) else this
private final class Not(delegate: Parser[?], failMessage: String) extends ValidParser[Unit]:
def derive(c: Char) = if delegate.valid then not(delegate.derive(c), failMessage) else this
def completions(level: Int) = Completions.empty
def result = None
lazy val resultEmpty = delegate.resultEmpty match {
lazy val resultEmpty = delegate.resultEmpty match
case _: Failure => Value(())
case _: Value[?] => mkFailure(failMessage)
}
override def toString = s" -(${delegate})"
}
/**
* This class wraps an existing parser (the delegate), and replaces the delegate's completions with
@@ -919,7 +857,7 @@ private final class ParserWithExamples[T](
exampleSource: ExampleSource,
maxNumberOfExamples: Int,
removeInvalidExamples: Boolean
) extends ValidParser[T] {
) extends ValidParser[T]:
def derive(c: Char) =
examples(
@@ -933,29 +871,24 @@ 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
else {
def completions(level: Int) =
if exampleSource().isEmpty then
if resultEmpty.isValid then Completions.nil else Completions.empty
else
val examplesBasedOnTheResult = filteredExamples.take(maxNumberOfExamples).toSet
Completions(examplesBasedOnTheResult.map(ex => Completion.suggestion(ex)))
}
}
override def toString = "examples(" + delegate + ", " + exampleSource().take(2).toList + ")"
private def filteredExamples: Iterable[String] = {
if (removeInvalidExamples)
exampleSource().filter(isExampleValid)
else
exampleSource()
}
private def filteredExamples: Iterable[String] =
if removeInvalidExamples then exampleSource().filter(isExampleValid)
else exampleSource()
private def isExampleValid(example: String): Boolean = {
private def isExampleValid(example: String): Boolean =
apply(delegate)(example).resultEmpty.isValid
}
}
end ParserWithExamples
private final class StringLiteral(str: String, start: Int) extends ValidParser[String] {
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]:
assert(0 <= start && start < str.length)
def failMsg = "Expected '" + str + "'"
@@ -964,28 +897,25 @@ private final class StringLiteral(str: String, start: Int) extends ValidParser[S
def result = None
def derive(c: Char) =
if (str.charAt(start) == c) stringLiteral(str, start + 1) else new Invalid(fail)
if str.charAt(start) == c then stringLiteral(str, start + 1) else new Invalid(fail)
def completions(level: Int) = Completions.single(Completion.suggestion(str.substring(start)))
override def toString = "\"" + str + "\""
}
private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char] {
private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char]:
def result = None
private def fail: Failure = mkFailure("Expected " + label)
def resultEmpty = fail
def derive(c: Char) = if (f(c)) success(c) else Invalid(fail)
def derive(c: Char) = if f(c) then success(c) else Invalid(fail)
def completions(level: Int) = Completions.empty
override def toString = "class(" + label + ")"
}
private final class Optional[A](delegate: Parser[A]) extends ValidParser[Option[A]] {
private final class Optional[A](delegate: Parser[A]) extends ValidParser[Option[A]]:
def result = delegate.result.map(some[A])
def resultEmpty = Value(None)
def derive(c: Char) = delegate.derive(c).map(some[A])
def completions(level: Int) = Completion.empty +: delegate.completions(level)
override def toString = delegate.toString + "?"
}
private final class Repeat[T](
partial: Option[Parser[T]],
@@ -993,56 +923,48 @@ private final class Repeat[T](
min: Int,
max: UpperBound,
accumulatedReverse: List[T]
) extends ValidParser[Seq[T]] {
) extends ValidParser[Seq[T]]:
assume(0 <= min, "Minimum occurrences must be non-negative")
assume(max >= min, "Minimum occurrences must be less than the maximum occurrences")
def derive(c: Char) =
partial match {
partial match
case Some(part) =>
val partD = repeat(Some(part.derive(c)), repeated, min, max, accumulatedReverse)
part.resultEmpty match {
part.resultEmpty match
case Value(pv) => partD | repeatDerive(c, pv :: accumulatedReverse)
case _: Failure => partD
}
case None => repeatDerive(c, accumulatedReverse)
}
def repeatDerive(c: Char, accRev: List[T]): Parser[Seq[T]] =
repeat(Some(repeated.derive(c)), repeated, scala.math.max(0, min - 1), max.decrement, accRev)
def completions(level: Int) = {
def completions(level: Int) =
@tailrec
def pow(comp: Completions, exp: Completions, n: Int): Completions =
if (n == 1) comp else pow(comp.x(exp), exp, n - 1)
if n == 1 then comp else pow(comp.x(exp), exp, n - 1)
val repC = repeated.completions(level)
val fin = if (min == 0) Completion.empty +: repC else pow(repC, repC, min)
partial match {
val fin = if min == 0 then Completion.empty +: repC else pow(repC, repC, min)
partial match
case Some(p) => p.completions(level).x(fin)
case None => fin
}
}
def result = None
lazy val resultEmpty: Result[Seq[T]] = {
lazy val resultEmpty: Result[Seq[T]] =
val partialAccumulatedOption =
partial match {
partial match
case None => (Value(accumulatedReverse): Result[List[T]])
case Some(partialPattern) =>
partialPattern.resultEmpty.map(_ :: accumulatedReverse)
}
(partialAccumulatedOption.app(repeatedParseEmpty))((x, y) => (x reverse_::: y): Seq[T])
}
private def repeatedParseEmpty: Result[List[T]] = {
if (min == 0)
Value(Nil)
private def repeatedParseEmpty: Result[List[T]] =
if min == 0 then Value(Nil)
else
// forced determinism
for (value <- repeated.resultEmpty) yield makeList(min, value)
}
for value <- repeated.resultEmpty yield makeList(min, value)
override def toString = "repeat(" + min + "," + max + "," + partial + "," + repeated + ")"
}
end Repeat
@@ -26,7 +26,7 @@ import scala.annotation.tailrec
import sbt.internal.util.Util.nilSeq
/** Provides standard implementations of commonly useful [[Parser]]s. */
trait Parsers {
trait Parsers:
/** Matches the end of input, providing no useful result on success. */
lazy val EOF = not(any, "Expected EOF")
@@ -116,22 +116,18 @@ trait Parsers {
start ~ rep.* map { case x ~ xs => (x +: xs).mkString }
def opOrIDSpaced(s: String): Parser[Char] =
if (DefaultParsers.matches(ID, s))
OpChar | SpaceClass
else if (DefaultParsers.matches(Op, s))
IDChar | SpaceClass
else
any
if DefaultParsers.matches(ID, s) then OpChar | SpaceClass
else if DefaultParsers.matches(Op, s) then IDChar | SpaceClass
else any
/** Returns true if `c` an operator character. */
def isOpChar(c: Char) = !isDelimiter(c) && isOpType(getType(c))
def isOpType(cat: Int) = cat match {
def isOpType(cat: Int) = cat match
case MATH_SYMBOL | OTHER_SYMBOL | DASH_PUNCTUATION | OTHER_PUNCTUATION | MODIFIER_SYMBOL |
CURRENCY_SYMBOL =>
true
case _ => false
}
/** Returns true if `c` is a dash `-`, a letter, digit, or an underscore `_`. */
def isIDChar(c: Char) = isScalaIDChar(c) || c == '-'
@@ -139,9 +135,9 @@ trait Parsers {
/** Returns true if `c` is a letter, digit, or an underscore `_`. */
def isScalaIDChar(c: Char) = c.isLetterOrDigit || c == '_'
def isDelimiter(c: Char) = c match {
case '`' | '\'' | '\"' | /*';' | */ ',' | '.' => true; case _ => false
}
def isDelimiter(c: Char) = c match
case '`' | '\'' | '\"' | /*';' | */ ',' | '.' => true;
case _ => false
/** Matches a single character that is not a whitespace character. */
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
@@ -187,14 +183,14 @@ trait Parsers {
/** Matches any character except a double quote or whitespace. */
lazy val NotDQuoteSpaceClass =
charClass(
(c: Char) => { (c != DQuoteChar) && !c.isWhitespace },
(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) },
(c: Char) => (c != DQuoteChar) && (c != BackslashChar),
"non-double-quote-backslash character"
)
@@ -281,9 +277,9 @@ trait Parsers {
* @return
* a parser for the brace enclosed string.
*/
private[sbt] def braces(open: Char, close: Char): Parser[String] = {
private[sbt] def braces(open: Char, close: Char): Parser[String] =
val notDelim = charClass(c => c != open && c != close).*.string
def impl(): Parser[String] = {
def impl(): Parser[String] =
(open ~ (notDelim ~ close).?).flatMap {
case (l, Some((content, r))) => Parser.success(s"$l$content$r")
case (l, None) =>
@@ -293,9 +289,7 @@ trait Parsers {
s"$l${nested.mkString}$suffix$r"
}
}
}
impl()
}
/**
* Parses a single escape sequence into the represented Char. Escapes start with a backslash and
@@ -351,9 +345,8 @@ trait Parsers {
*/
def mapOrFail[S, T](p: Parser[S])(f: S => T): Parser[T] =
p flatMap { s =>
try {
success(f(s))
} catch { case e: Exception => failure(e.toString) }
try success(f(s))
catch case e: Exception => failure(e.toString)
}
/**
@@ -376,17 +369,15 @@ trait Parsers {
* 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]] = {
def repeatDep[A](p: Seq[A] => Parser[A], sep: Parser[Any]): Parser[Seq[A]] =
def loop(acc: Seq[A]): Parser[Seq[A]] =
val next = (sep ~> p(acc)) flatMap { result =>
loop(acc :+ result)
}
next ?? acc
}
p(Vector()) flatMap { first =>
loop(Seq(first))
}
}
/** Applies String.trim to the result of `p`. */
def trimmed(p: Parser[String]) = p map { _.trim }
@@ -399,29 +390,24 @@ trait Parsers {
* `ex` as tab completion examples.
*/
def Uri(ex: Set[URI]) = basicUri.examples(ex.map(_.toString))
}
end Parsers
/** Provides standard [[Parser]] implementations. */
object Parsers extends Parsers
/** Provides common [[Parser]] implementations and helper methods. */
object DefaultParsers extends Parsers with ParserMain {
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]]. */
def validID(s: String): Boolean = {
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 == '_')
@tailrec def isRestIdChar(cur: Int, s: String, length: Int): Boolean =
if (cur < length)
isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length)
else
true
if cur < length then isIdChar(s.charAt(cur)) && isRestIdChar(cur + 1, s, length)
else true
!s.isEmpty && Character.isLetter(s.charAt(0)) && isRestIdChar(1, s, s.length)
}
}
@@ -11,32 +11,26 @@ package complete
import scala.annotation.tailrec
object ProcessError {
def apply(command: String, msgs: Seq[String], index: Int): String = {
object ProcessError:
def apply(command: String, msgs: Seq[String], index: Int): String =
val (line, modIndex) = extractLine(command, index)
val point = pointerSpace(command, modIndex)
msgs.mkString("\n") + "\n" + line + "\n" + point + "^"
}
def extractLine(s: String, i: Int): (String, Int) = {
def extractLine(s: String, i: Int): (String, Int) =
val notNewline = (c: Char) => c != '\n' && c != '\r'
val left = takeRightWhile(s.substring(0, i))(notNewline)
val right = s.substring(i).takeWhile(notNewline)
(left + right, left.length)
}
def takeRightWhile(s: String)(pred: Char => Boolean): String = {
def takeRightWhile(s: String)(pred: Char => Boolean): String =
@tailrec
def loop(i: Int): String =
if (i < 0)
s
else if (pred(s(i)))
loop(i - 1)
else
s.substring(i + 1)
if i < 0 then s
else if pred(s(i)) then loop(i - 1)
else s.substring(i + 1)
loop(s.length - 1)
}
def pointerSpace(s: String, i: Int): String =
(s take i) map { case '\t' => '\t'; case _ => ' ' } mkString ""
}
end ProcessError
@@ -10,21 +10,18 @@ package sbt.internal.util.complete
import sbt.internal.util.complete.DefaultParsers.*
private[sbt] object SizeParser {
private[sbt] object SizeParser:
def apply(s: String): Option[Long] = Parser.parse(s, value).toOption
private enum SizeUnit {
private enum SizeUnit:
case Bytes, KiloBytes, MegaBytes, GigaBytes
}
private def parseDouble(s: String): Parser[Either[Double, Long]] =
s.toDoubleOption match {
s.toDoubleOption match
case Some(x) => Parser.success(Left(x))
case _ => Parser.failure(s"Couldn't parse $s as double.")
}
private def parseLong(s: String): Parser[Either[Double, Long]] =
s.toLongOption match {
s.toLongOption match
case Some(x) => Parser.success(Right(x))
case _ => Parser.failure(s"Couldn't parse $s as double.")
}
private val digit = charClass(_.isDigit, "digit")
private val numberParser: Parser[Either[Double, Long]] =
(digit.+ ~ ('.'.examples() ~> digit.+).?).flatMap {
@@ -39,20 +36,18 @@ private[sbt] object SizeParser {
case "k" | "K" => SizeUnit.KiloBytes
case "m" | "M" => SizeUnit.MegaBytes
}
private def multiply(left: Either[Double, Long], right: Long): Long = left match {
private def multiply(left: Either[Double, Long], right: Long): Long = left match
case Left(d) => (d * right).toLong
case Right(l) => l * right
}
private[sbt] val value: Parser[Long] =
((numberParser <~ SpaceClass
.examples(" ", "b", "B", "g", "G", "k", "K", "m", "M")
.*) ~ unitParser.?)
.map { (number, unit) =>
unit match {
unit match
case None | Some(SizeUnit.Bytes) => multiply(number, right = 1L)
case Some(SizeUnit.KiloBytes) => multiply(number, right = 1024L)
case Some(SizeUnit.MegaBytes) => multiply(number, right = 1024L * 1024)
case Some(SizeUnit.GigaBytes) => multiply(number, right = 1024L * 1024 * 1024)
}
}
}
end SizeParser
@@ -11,43 +11,37 @@ package complete
import Completion.{ token as ctoken, tokenDisplay }
sealed trait TokenCompletions {
sealed trait TokenCompletions:
def hideWhen(f: Int => Boolean): TokenCompletions
}
object TokenCompletions {
private[sbt] abstract class Delegating extends TokenCompletions { outer =>
object TokenCompletions:
private[sbt] abstract class Delegating extends TokenCompletions:
outer =>
def completions(seen: String, level: Int, delegate: Completions): Completions
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Delegating {
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Delegating:
def completions(seen: String, level: Int, delegate: Completions): Completions =
if (hide(level)) Completions.nil else outer.completions(seen, level, delegate)
}
}
if hide(level) then Completions.nil else outer.completions(seen, level, delegate)
private[sbt] abstract class Fixed extends TokenCompletions { outer =>
private[sbt] abstract class Fixed extends TokenCompletions:
outer =>
def completions(seen: String, level: Int): Completions
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Fixed {
final def hideWhen(hide: Int => Boolean): TokenCompletions = new Fixed:
def completions(seen: String, level: Int) =
if (hide(level)) Completions.nil else outer.completions(seen, level)
}
}
if hide(level) then Completions.nil else outer.completions(seen, level)
val default: TokenCompletions = mapDelegateCompletions((seen, level, c) => ctoken(seen, c.append))
def displayOnly(msg: String): TokenCompletions = new Fixed {
def displayOnly(msg: String): TokenCompletions = new Fixed:
def completions(seen: String, level: Int) = Completions.single(Completion.displayOnly(msg))
}
def overrideDisplay(msg: String): TokenCompletions =
mapDelegateCompletions((seen, level, c) => tokenDisplay(display = msg, append = c.append))
def fixed(f: (String, Int) => Completions): TokenCompletions = new Fixed {
def fixed(f: (String, Int) => Completions): TokenCompletions = new Fixed:
def completions(seen: String, level: Int) = f(seen, level)
}
def mapDelegateCompletions(f: (String, Int, Completion) => Completion): TokenCompletions =
new Delegating {
new Delegating:
def completions(seen: String, level: Int, delegate: Completions) =
Completions(delegate.get.map(c => f(seen, level, c)))
}
}
end TokenCompletions
@@ -17,24 +17,20 @@ import TypeString.*
* 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]) {
private[sbt] final class TypeString(val base: String, val args: List[TypeString]):
override def toString =
if (base.startsWith(FunctionName))
if base.startsWith(FunctionName) then
args.dropRight(1).mkString("(", ",", ")") + " => " + args.last
else if (base.startsWith(TupleName))
args.mkString("(", ",", ")")
else
cleanupTypeName(base) + (if (args.isEmpty) "" else args.mkString("[", ",", "]"))
}
else if base.startsWith(TupleName) then args.mkString("(", ",", ")")
else cleanupTypeName(base) + (if args.isEmpty then "" else args.mkString("[", ",", "]"))
private[sbt] object TypeString {
private[sbt] object TypeString:
/** Makes the string representation of a type as returned by Manifest.toString more readable. */
def cleanup(typeString: String): String =
parse(typeString, typeStringParser) match {
parse(typeString, typeStringParser) match
case Right(ts) => ts.toString
case Left(_) => typeString
}
/**
* Makes a fully qualified type name provided by Manifest.toString more readable. The argument
@@ -49,16 +45,13 @@ private[sbt] object TypeString {
* conservative approximation.
*/
def dropPrefix(base: String): String =
if (base.startsWith(SbtPrefix)) base.substring(SbtPrefix.length)
else if (base.startsWith(CollectionPrefix)) {
if base.startsWith(SbtPrefix) then base.substring(SbtPrefix.length)
else if base.startsWith(CollectionPrefix) then
val simple = base.substring(CollectionPrefix.length)
if (ShortenCollection(simple)) simple else base
} else if (base.startsWith(ScalaPrefix))
base.substring(ScalaPrefix.length)
else if (base.startsWith(JavaPrefix))
base.substring(JavaPrefix.length)
else
TypeMap.getOrElse(base, base)
if ShortenCollection(simple) then simple else base
else if base.startsWith(ScalaPrefix) then base.substring(ScalaPrefix.length)
else if base.startsWith(JavaPrefix) then base.substring(JavaPrefix.length)
else TypeMap.getOrElse(base, base)
final val CollectionPrefix = "scala.collection."
final val FunctionName = "scala.Function"
@@ -80,13 +73,12 @@ private[sbt] object TypeString {
* Manifest.toString. This is rudimentary and essentially only decomposes the string into names
* and arguments for parameterized types.
*/
lazy val typeStringParser: Parser[TypeString] = {
lazy val typeStringParser: Parser[TypeString] =
def isFullScalaIDChar(c: Char) = isScalaIDChar(c) || c == '.' || c == '$'
lazy val fullScalaID =
identifier(IDStart, charClass(isFullScalaIDChar, "Scala identifier character"))
lazy val tpe: Parser[TypeString] =
for (id <- fullScalaID; args <- ('[' ~> rep1sep(tpe, ',') <~ ']').?)
yield new TypeString(id, args.toList.flatten)
for id <- fullScalaID; args <- ('[' ~> rep1sep(tpe, ',') <~ ']').?
yield new TypeString(id, args.toList.flatten)
tpe
}
}
end TypeString
@@ -9,7 +9,7 @@
package sbt.internal.util
package complete
sealed trait UpperBound {
sealed trait UpperBound:
/** True if and only if the given value meets this bound. */
def >=(min: Int): Boolean
@@ -30,10 +30,8 @@ sealed trait UpperBound {
/** True if and only if this is unbounded. */
def isInfinite: Boolean
}
/** Represents unbounded. */
case object Infinite extends UpperBound {
case object Infinite extends UpperBound:
/** All finite numbers meet this bound. */
def >=(min: Int) = true
@@ -44,13 +42,12 @@ case object Infinite extends UpperBound {
def isInfinite = true
override def toString = "Infinity"
}
/**
* Represents a finite upper bound. The maximum allowed value is 'value', inclusive. It must
* positive.
*/
final case class Finite(value: Int) extends UpperBound {
final case class Finite(value: Int) extends UpperBound:
assume(value >= 0, "Maximum occurrences must be nonnegative.")
def >=(min: Int) = value >= min
@@ -59,8 +56,6 @@ final case class Finite(value: Int) extends UpperBound {
def decrement = Finite(scala.math.max(0, value - 1))
def isInfinite = false
override def toString = value.toString
}
object UpperBound {
object UpperBound:
implicit def intToFinite(i: Int): Finite = Finite(i)
}
@@ -11,7 +11,7 @@ package complete
import org.scalacheck.*, Gen.*, Prop.*
object DefaultParsersSpec extends Properties("DefaultParsers") {
object DefaultParsersSpec extends Properties("DefaultParsers"):
import DefaultParsers.{ ID, isIDChar, matches, validID }
property("∀ s ∈ String: validID(s) == matches(ID, s)") =
@@ -22,8 +22,7 @@ object DefaultParsersSpec extends Properties("DefaultParsers") {
private val chars: Seq[Char] = Char.MinValue to Char.MaxValue
private val genID: Gen[String] =
for {
for
c <- oneOf(chars filter (_.isLetter))
cs <- listOf(oneOf(chars filter isIDChar))
} yield (c :: cs).mkString
}
yield (c :: cs).mkString
@@ -11,7 +11,7 @@ package complete
import scala.annotation.tailrec
object JLineTest {
object JLineTest:
import DefaultParsers.*
val one = "blue" | "green" | "black"
@@ -27,7 +27,7 @@ object JLineTest {
}
val parsers = Map("1" -> one, "2" -> two, "3" -> three, "4" -> four, "5" -> five)
def main(args: Array[String]): Unit = {
def main(args: Array[String]): Unit =
import jline.TerminalFactory
import jline.console.ConsoleReader
val reader = new ConsoleReader()
@@ -36,21 +36,18 @@ object JLineTest {
val parser = parsers(args(0))
JLineCompletion.installCustomCompletor(reader, parser)
@tailrec
def loop(): Unit = {
def loop(): Unit =
val line = reader.readLine("> ")
if (line ne null) {
if line ne null then
println("Result: " + apply(parser)(line).resultEmpty)
loop()
}
}
loop()
}
}
end JLineTest
import Parser.*
import org.scalacheck.*
object ParserTest extends Properties("Completing Parser") {
object ParserTest extends Properties("Completing Parser"):
import Parsers.*
import DefaultParsers.matches
@@ -59,7 +56,8 @@ object ParserTest extends Properties("Completing Parser") {
val spacePort = token(Space) ~> Port
def p[T](f: T): T = { println(f); f }
def p[T](f: T): T =
println(f); f
def checkSingle(in: String, expect: Completion)(expectDisplay: Completion = expect) =
(("token '" + in + "'") |: checkOne(in, nested, expect)) &&
@@ -68,19 +66,17 @@ object ParserTest extends Properties("Completing Parser") {
def checkOne(in: String, parser: Parser[?], expect: Completion): Prop =
completions(parser, in, 1) == Completions.single(expect)
def checkAll(in: String, parser: Parser[?], expect: Completions): Prop = {
def checkAll(in: String, parser: Parser[?], expect: Completions): Prop =
val cs = completions(parser, in, 1)
("completions: " + cs) |: ("Expected: " + expect) |: (cs == expect: Prop)
}
def checkInvalid(in: String) =
(("token '" + in + "'") |: checkInv(in, nested)) &&
(("display '" + in + "'") |: checkInv(in, nestedDisplay))
def checkInv(in: String, parser: Parser[?]): Prop = {
def checkInv(in: String, parser: Parser[?]): Prop =
val cs = completions(parser, in, 1)
("completions: " + cs) |: (cs == Completions.nil: Prop)
}
property("nested tokens a") =
checkSingle("", Completion.token("", "a1"))(Completion.displayOnly("<a1>"))
@@ -113,21 +109,19 @@ object ParserTest extends Properties("Completing Parser") {
property("repeatDep no suggestions for bad input") = checkInv(".", repeat)
property("repeatDep suggest all") = checkAll("", repeat, completionStrings(colors))
property("repeatDep suggest remaining two") = {
property("repeatDep suggest remaining two") =
val first = colors.toSeq.head
checkAll(first + " ", repeat, completionStrings(colors - first))
}
property("repeatDep suggest remaining one") = {
property("repeatDep suggest remaining one") =
val take = colors.toSeq.take(2)
checkAll(take.mkString("", " ", " "), repeat, completionStrings(colors -- take))
}
property("repeatDep requires at least one token") = !matches(repeat, "")
property("repeatDep accepts one token") = matches(repeat, colors.toSeq.head)
property("repeatDep accepts two tokens") = matches(repeat, colors.toSeq.take(2).mkString(" "))
property("parses string that doesn't start with quotes, but includes quotes within it") =
matches(StringBasic, "-Dsilicon:z3ConfigArgs=\"model=true model_validate=true\"")
}
object ParserExample {
end ParserTest
object ParserExample:
val ws = charClass(_.isWhitespace, "whitespace").+
val notws = charClass(!_.isWhitespace, "not whitespace").+
@@ -151,7 +145,7 @@ object ParserExample {
println(apply(t)("test w").resultEmpty)
println(apply(t)("test was were").resultEmpty)
def run(n: Int): Unit = {
def run(n: Int): Unit =
val a = 'a'.id
val aq = a.?
val aqn = repeat(aq, min = n, max = n)
@@ -160,10 +154,8 @@ object ParserExample {
def r = apply(ann)("a" * (n * 2)).resultEmpty
println(r.isValid)
}
def run2(n: Int): Unit = {
def run2(n: Int): Unit =
val ab = "ab".?.*
val r = apply(ab)("a" * n).resultEmpty
println(r)
}
}
end ParserExample
@@ -13,7 +13,7 @@ import java.io.File
import org.scalatest.Assertion
import sbt.io.IO
class FileExamplesTest extends UnitSpec {
class FileExamplesTest extends UnitSpec:
"listing all files in an absolute base directory" should
"produce the entire base directory's contents" in {
@@ -60,16 +60,15 @@ class FileExamplesTest extends UnitSpec {
def withDirectoryStructure(withCompletionPrefix: String = "")(
thunk: DirectoryStructure => Assertion
): Assertion = {
): Assertion =
IO.withTemporaryDirectory { tempDir =>
val ds = new DirectoryStructure(withCompletionPrefix)
ds.createSampleDirStructure(tempDir)
ds.fileExamples = new FileExamples(ds.baseDir, withCompletionPrefix)
thunk(ds)
}
}
final class DirectoryStructure(withCompletionPrefix: String) {
final class DirectoryStructure(withCompletionPrefix: String):
var fileExamples: FileExamples = scala.compiletime.uninitialized
var baseDir: File = scala.compiletime.uninitialized
var childFiles: List[File] = scala.compiletime.uninitialized
@@ -86,7 +85,7 @@ class FileExamplesTest extends UnitSpec {
.withFilter(_.startsWith(withCompletionPrefix))
.map(_.substring(withCompletionPrefix.length))
def createSampleDirStructure(tempDir: File): Unit = {
def createSampleDirStructure(tempDir: File): Unit =
childFiles = toChildFiles(tempDir, List("foo", "bar", "bazaar"))
childDirectories = toChildFiles(tempDir, List("moo", "far"))
nestedFiles = toChildFiles(childDirectories(1), List("farfile1", "barfile2"))
@@ -96,10 +95,8 @@ class FileExamplesTest extends UnitSpec {
(childFiles ++ nestedFiles).foreach(_.createNewFile())
baseDir = tempDir
}
private def toChildFiles(baseDir: File, files: List[String]): List[File] =
files.map(new File(baseDir, _))
}
}
end DirectoryStructure
end FileExamplesTest
@@ -9,25 +9,21 @@
package sbt.internal.util
package complete
class FixedSetExamplesTest extends UnitSpec {
class FixedSetExamplesTest extends UnitSpec:
"adding a prefix" should "produce a smaller set of examples with the prefix removed" in {
val _ = new Examples {
val _ = new Examples:
fixedSetExamples.withAddedPrefix("f")() should contain theSameElementsAs
(List("oo", "ool", "u"))
fixedSetExamples.withAddedPrefix("fo")() should contain theSameElementsAs (List("o", "ol"))
fixedSetExamples.withAddedPrefix("b")() should contain theSameElementsAs (List("ar"))
}
}
"without a prefix" should "produce the original set" in {
val _ = new Examples {
val _ = new Examples:
fixedSetExamples() shouldBe exampleSet
}
}
trait Examples {
trait Examples:
val exampleSet = List("foo", "bar", "fool", "fu")
val fixedSetExamples = FixedSetExamples(exampleSet)
}
}
@@ -11,19 +11,18 @@ package complete
import Completion.*
class ParserWithExamplesTest extends UnitSpec {
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 {
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 _ = new ParserWithValidExamples:
val validCompletions = Completions(
Set(
suggestion("blue"),
@@ -31,32 +30,29 @@ class ParserWithExamplesTest extends UnitSpec {
)
)
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 _ = new ParserWithValidExamples:
val derivedCompletions = Completions(
Set(
suggestion("lue")
)
)
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
}
}
"listing valid and invalid completions" should
"produce the entire source of examples" in {
val _ = new parserWithAllExamples {
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 _ = new parserWithAllExamples:
val derivedCompletions = Completions(
Set(
suggestion("lue"),
@@ -64,7 +60,6 @@ class ParserWithExamplesTest extends UnitSpec {
)
)
parserWithExamples.derive('b').completions(0) shouldEqual derivedCompletions
}
}
class ParserWithLazyExamples
@@ -82,7 +77,7 @@ class ParserWithExamplesTest extends UnitSpec {
examples: Iterable[String] = Set("blue", "yellow", "green_", "block", "red"),
maxNumberOfExamples: Int = 25,
removeInvalidExamples: Boolean
) {
):
import DefaultParsers.*
@@ -93,26 +88,20 @@ class ParserWithExamplesTest extends UnitSpec {
maxNumberOfExamples,
removeInvalidExamples
)
}
case class GrowableSourceOfExamples() extends Iterable[String] {
case class GrowableSourceOfExamples() extends Iterable[String]:
private var numberOfIteratedElements: Int = 0
override def iterator: Iterator[String] = {
new Iterator[String] {
override def iterator: Iterator[String] =
new Iterator[String]:
var currentElement = 0
override def next(): String = {
override def next(): String =
currentElement += 1
numberOfIteratedElements = Math.max(currentElement, numberOfIteratedElements)
numberOfIteratedElements.toString
}
override def hasNext: Boolean = true
}
}
override def size: Int = numberOfIteratedElements
}
}
end ParserWithExamplesTest
@@ -10,19 +10,16 @@ package sbt.internal.util
import java.io.IOException
object ErrorHandling {
object ErrorHandling:
def translate[T](msg: => String)(f: => T) =
try {
f
} catch {
try f
catch
case e: IOException => throw new TranslatedIOException(msg + e.toString, e)
case e: Exception => throw new TranslatedException(msg + e.toString, e)
}
def wideConvert[T](f: => T): Either[Throwable, T] =
try {
Right(f)
} catch {
try Right(f)
catch
case ex @ (_: Exception | _: StackOverflowError) =>
Left(ex)
case err: VirtualMachineError =>
@@ -33,24 +30,21 @@ object ErrorHandling {
throw err
case x: Throwable =>
Left(x)
}
def convert[T](f: => T): Either[Exception, T] =
try {
Right(f)
} catch { case e: Exception => Left(e) }
try Right(f)
catch case e: Exception => Left(e)
def reducedToString(e: Throwable): String =
if (e.getClass == classOf[RuntimeException]) {
if e.getClass == classOf[RuntimeException] then
val msg = e.getMessage
if (msg == null || msg.isEmpty) e.toString else msg
} else e.toString
}
if msg == null || msg.isEmpty then e.toString else msg
else e.toString
end ErrorHandling
sealed class TranslatedException private[sbt] (msg: String, cause: Throwable)
extends RuntimeException(msg, cause) {
extends RuntimeException(msg, cause):
override def toString = msg
}
final class TranslatedIOException private[sbt] (msg: String, cause: IOException)
extends TranslatedException(msg, cause)
@@ -9,18 +9,16 @@
package sbt.internal.util
/** Defines a function to call as sbt exits. */
trait ExitHook {
trait ExitHook:
/** Subclasses should implement this method, which is called when this hook is executed. */
def runBeforeExiting(): Unit
}
object ExitHook:
def apply(f: => Unit): ExitHook = new ExitHook:
def runBeforeExiting() = f
object ExitHook {
def apply(f: => Unit): ExitHook = new ExitHook { def runBeforeExiting() = f }
}
object ExitHooks {
object ExitHooks:
/**
* Calls each registered exit hook, trapping any exceptions so that each hook is given a chance to
@@ -28,5 +26,3 @@ object ExitHooks {
*/
def runExitHooks(exitHooks: Seq[ExitHook]): Seq[Throwable] =
exitHooks.flatMap(hook => ErrorHandling.wideConvert(hook.runBeforeExiting()).left.toOption)
}
@@ -17,7 +17,7 @@ import scala.sys.process.Process
* killed with the killAll method. In particular, this can be used in a signal handler to kill these
* processes when the user inputs ctrl+c.
*/
private[sbt] object RunningProcesses {
private[sbt] object RunningProcesses:
val active = ConcurrentHashMap.newKeySet[AnyRef]
def add(process: AnyRef): Unit = active.synchronized {
active.add(process)
@@ -35,4 +35,3 @@ private[sbt] object RunningProcesses {
}
active.clear()
}
}
@@ -143,6 +143,7 @@ object FarmHash64:
off += 64
off != fin
do ()
end while
off = last64.toInt
@@ -183,6 +184,8 @@ object FarmHash64:
z = x
x = t
hashLen16(hashLen16(v1, w1, m) + shiftMix(y) * K0 + z, hashLen16(v2, w2, m) + x, m)
end if
end naHash64
end FarmHash64
object FarmNaSeedlessHash64:
@@ -56,6 +56,7 @@ class StreamingXXHash64VarHandle(seed: Long) extends AbstractStreamingXXHash64Sc
h64 ^= v4
h64 = h64 * PRIME64_1 + PRIME64_4
else h64 = seed + PRIME64_5
end if
h64 += totalLen
@@ -123,6 +124,7 @@ class StreamingXXHash64VarHandle(seed: Long) extends AbstractStreamingXXHash64Sc
off += 32 - memSize
memSize = 0
else ()
end if
{
val limit: Int = end - 32
@@ -71,6 +71,7 @@ class XXHash64[A1: Access](seed: Long) extends HashAlgo[A1]:
off += 8
off <= limit
do ()
end while
h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18)
@@ -98,6 +99,7 @@ class XXHash64[A1: Access](seed: Long) extends HashAlgo[A1]:
h64 ^= v4
h64 = h64 * PRIME64_1 + PRIME64_4
else h64 = seed + PRIME64_5
end if
h64 += len
@@ -9,10 +9,9 @@ package sbt.internal.util.appmacro
final class StringTypeTag[A](val key: String):
override def toString(): String = key
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match
case x: StringTypeTag[?] => (this.key == x.key)
case _ => false
})
case _ => false)
override def hashCode: Int = key.##
end StringTypeTag
@@ -19,16 +19,14 @@ import scala.util.control.NonFatal
object Util:
def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value)
def separate[T, A, B](ps: Seq[T])(f: T => Either[A, B]): (Seq[A], Seq[B]) = {
def separate[T, A, B](ps: Seq[T])(f: T => Either[A, B]): (Seq[A], Seq[B]) =
val (a, b) = ps.foldLeft((Nil: Seq[A], Nil: Seq[B]))((xs, y) => prependEither(xs, f(y)))
(a.reverse, b.reverse)
}
def prependEither[A, B](acc: (Seq[A], Seq[B]), next: Either[A, B]): (Seq[A], Seq[B]) =
next match {
next match
case Left(l) => (l +: acc._1, acc._2)
case Right(r) => (acc._1, r +: acc._2)
}
def pairID[A, B] = (a: A, b: B) => (a, b)
@@ -37,14 +35,14 @@ object Util:
def hasHyphen(s: String): Boolean = s.indexOf('-') >= 0
def hyphenToCamel(s: String): String =
if (hasHyphen(s)) Hyphen.replaceAllIn(s, _.group(1).toUpperCase(Locale.ENGLISH)) else s
if hasHyphen(s) then Hyphen.replaceAllIn(s, _.group(1).toUpperCase(Locale.ENGLISH)) else s
private lazy val Camel = """(\p{javaLowerCase})(\p{javaUpperCase})""".r
def camelToHyphen(s: String): String =
Camel.replaceAllIn(s, m => m.group(1) + "-" + m.group(2).toLowerCase(Locale.ENGLISH))
def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) s"`${s}`" else s
def quoteIfKeyword(s: String): String = if ScalaKeywords.values(s) then s"`${s}`" else s
def quoteIfNotScalaId(s: String): String =
if isValidScalaId(s) && !ScalaKeywords.values(s) then s
@@ -54,10 +52,9 @@ object Util:
s.nonEmpty && (s.charAt(0).isLetter || s.charAt(0) == '_') &&
s.forall(c => c.isLetterOrDigit || c == '_')
def ignoreResult[A](f: => A): Unit = {
def ignoreResult[A](f: => A): Unit =
val _ = f
()
}
def ignoreTry[A](f: => A): Unit = ignoreResult(Try(f))
@@ -67,13 +64,11 @@ object Util:
lazy val isWindows: Boolean =
System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")
lazy val isCygwin: Boolean = {
lazy val isCygwin: Boolean =
val os = sys.env.get("OSTYPE")
os match {
os match
case Some(x) => x.toLowerCase(Locale.ENGLISH).contains("cygwin")
case _ => false
}
}
lazy val isNonCygwinWindows: Boolean = isWindows && !isCygwin
lazy val isCygwinWindows: Boolean = isWindows && isCygwin
@@ -88,14 +83,11 @@ object Util:
def nilSeq[A]: Seq[A] = Seq.empty[A]
def none[A]: Option[A] = (None: Option[A])
extension [A](value: A) {
def some: Option[A] = (Some(value): Option[A])
}
extension [A](value: A) def some: Option[A] = (Some(value): Option[A])
private[sbt] def withCaching[A1, A2](f: A1 => A2): A1 => A2 = {
private[sbt] def withCaching[A1, A2](f: A1 => A2): A1 => A2 =
val cache = TrieMap.empty[A1, A2]
x => cache.getOrElseUpdate(x, f(x))
}
lazy val javaHome: Path =
sys.env.get("JAVA_HOME") match
@@ -11,7 +11,7 @@ package sbt.internal.util
import sbt.util.*
/** Implements the level-setting methods of Logger. */
abstract class BasicLogger extends AbstractLogger {
abstract class BasicLogger extends AbstractLogger:
private var traceEnabledVar: Int = java.lang.Integer.MAX_VALUE
private var level: Level.Value = Level.Info
private var successEnabledVar = true
@@ -21,4 +21,3 @@ abstract class BasicLogger extends AbstractLogger {
def setLevel(newLevel: Level.Value): Unit = synchronized { level = newLevel }
def setTrace(level: Int): Unit = synchronized { traceEnabledVar = level }
def getTrace: Int = synchronized { traceEnabledVar }
}
@@ -12,7 +12,7 @@ import sbt.util.*
import scala.collection.mutable.ListBuffer
import java.util.concurrent.atomic.AtomicInteger
object BufferedAppender {
object BufferedAppender:
def generateName: String =
"buffered-" + generateId.incrementAndGet
@@ -23,7 +23,6 @@ object BufferedAppender {
def apply(name: String, delegate: Appender): BufferedAppender =
new BufferedAppender(name, delegate)
}
/**
* An appender that can buffer the logging done on it and then can flush the buffer to the delegate
@@ -31,7 +30,7 @@ object BufferedAppender {
* 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 {
class BufferedAppender(override val name: String, delegate: Appender) extends Appender:
override def close(): Unit = ()
override private[sbt] def properties: ConsoleAppender.Properties = delegate.properties
override private[sbt] def suppressedMessage: SuppressedTraceContext => Option[String] =
@@ -41,40 +40,37 @@ class BufferedAppender(override val name: String, delegate: Appender) extends Ap
new java.util.Vector[(Level.Value, Option[String], Option[ObjectEvent[?]])]
private var recording = false
override def appendLog(level: Level.Value, message: => String): Unit = {
if (recording) Util.ignoreResult(buffer.add((level, Some(message), None)))
override def appendLog(level: Level.Value, message: => String): Unit =
if recording then Util.ignoreResult(buffer.add((level, Some(message), None)))
else delegate.appendLog(level, message)
}
override private[sbt] def appendObjectEvent[T](
level: Level.Value,
message: => ObjectEvent[T]
): Unit = {
if (recording) Util.ignoreResult(buffer.add(((level, None, Some(message)))))
): Unit =
if recording then Util.ignoreResult(buffer.add(((level, None, Some(message)))))
else delegate.appendObjectEvent(level, message)
}
/** Enables buffering. */
def record() = synchronized { recording = true }
def buffer[T](f: => T): T = {
def buffer[T](f: => T): T =
record()
try {
try
f
} finally {
finally
stopQuietly()
}
}
def bufferQuietly[T](f: => T): T = {
def bufferQuietly[T](f: => T): T =
record()
try {
try
val result = f
clearBuffer()
result
} catch { case e: Throwable => stopQuietly(); throw e }
}
catch
case e: Throwable =>
stopQuietly()
throw e
def stopQuietly() = synchronized {
try {
stopBuffer()
} catch { case _: Exception => () }
try stopBuffer()
catch case _: Exception => ()
}
/**
@@ -95,8 +91,7 @@ class BufferedAppender(override val name: String, delegate: Appender) extends Ap
/** Plays buffered events and disables buffering. */
def stopBuffer(): Unit = synchronized { play(); clearBuffer() }
}
end BufferedAppender
/**
* A logger that can buffer the logging done on it and then can flush the buffer to the delegate
@@ -106,32 +101,31 @@ class BufferedAppender(override val name: String, delegate: Appender) extends Ap
*
* This class assumes that it is the only client of the delegate logger.
*/
class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
class BufferedLogger(delegate: AbstractLogger) extends BasicLogger:
private val buffer = new ListBuffer[LogEvent]
private var recording = false
/** Enables buffering. */
def record() = synchronized { recording = true }
def buffer[T](f: => T): T = {
def buffer[T](f: => T): T =
record()
try {
try
f
} finally {
finally
stopQuietly()
}
}
def bufferQuietly[T](f: => T): T = {
def bufferQuietly[T](f: => T): T =
record()
try {
try
val result = f
clear()
result
} catch { case e: Throwable => stopQuietly(); throw e }
}
catch
case e: Throwable =>
stopQuietly()
throw e
def stopQuietly() = synchronized {
try {
stop()
} catch { case _: Exception => () }
try stop()
catch case _: Exception => ()
}
/**
@@ -148,28 +142,22 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
override def setLevel(newLevel: Level.Value): Unit = synchronized {
super.setLevel(newLevel)
if (recording)
buffer += new SetLevel(newLevel)
else
delegate.setLevel(newLevel)
if recording then buffer += new SetLevel(newLevel)
else delegate.setLevel(newLevel)
()
}
override def setSuccessEnabled(flag: Boolean): Unit = synchronized {
super.setSuccessEnabled(flag)
if (recording)
buffer += new SetSuccess(flag)
else
delegate.setSuccessEnabled(flag)
if recording then buffer += new SetSuccess(flag)
else delegate.setSuccessEnabled(flag)
()
}
override def setTrace(level: Int): Unit = synchronized {
super.setTrace(level)
if (recording)
buffer += new SetTrace(level)
else
delegate.setTrace(level)
if recording then buffer += new SetTrace(level)
else delegate.setTrace(level)
()
}
@@ -182,10 +170,8 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
doBufferable(level, new Log(level, message), _.log(level, message))
def logAll(events: Seq[LogEvent]): Unit = synchronized {
if (recording)
buffer ++= events
else
delegate.logAll(events)
if recording then buffer ++= events
else delegate.logAll(events)
()
}
@@ -204,12 +190,9 @@ class BufferedLogger(delegate: AbstractLogger) extends BasicLogger {
appendIfBuffered: => LogEvent,
doUnbuffered: AbstractLogger => Unit
): Unit = synchronized {
if (condition) {
if (recording)
buffer += appendIfBuffered
else
doUnbuffered(delegate)
}
if condition then
if recording then buffer += appendIfBuffered
else doUnbuffered(delegate)
()
}
}
end BufferedLogger
@@ -15,7 +15,7 @@ import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
import sbt.internal.util.ConsoleAppender.*
import sbt.util.*
object ConsoleLogger {
object ConsoleLogger:
/**
* A new `ConsoleLogger` that logs to `out`.
@@ -59,7 +59,7 @@ object ConsoleLogger {
ConsoleAppender.noSuppressedMessage
): ConsoleLogger =
new ConsoleLogger(out, ansiCodesSupported, useFormat, suppressedMessage)
}
end ConsoleLogger
/**
* A logger that logs to the console. On supported systems, the level labels are colored.
@@ -69,7 +69,7 @@ class ConsoleLogger private[ConsoleLogger] (
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
) extends BasicLogger {
) extends BasicLogger:
private[sbt] val appender: Appender =
ConsoleAppender(generateName(), out, ansiCodesSupported, useFormat, suppressedMessage)
@@ -78,22 +78,18 @@ class ConsoleLogger private[ConsoleLogger] (
appender.control(event, message)
override def log(level: Level.Value, message: => String): Unit =
if (atLevel(level)) {
appender.appendLog(level, message)
}
if atLevel(level) then appender.appendLog(level, message)
override def success(message: => String): Unit =
if (successEnabled) {
appender.success(message)
}
if successEnabled then appender.success(message)
override def trace(t: => Throwable): Unit =
appender.trace(t, getTrace)
override def logAll(events: Seq[LogEvent]) = events.foreach(log)
}
end ConsoleLogger
object ConsoleAppender {
object ConsoleAppender:
private[sbt] def cursorLeft(n: Int): String = s"\u001B[${n}D"
private[sbt] def cursorUp(n: Int): String = s"\u001B[${n}A"
private[sbt] def cursorDown(n: Int): String = s"\u001B[${n}B"
@@ -108,24 +104,21 @@ object ConsoleAppender {
private val showProgressHolder: AtomicBoolean = new AtomicBoolean(false)
def setShowProgress(b: Boolean): Unit = showProgressHolder.set(b)
def showProgress: Boolean = showProgressHolder.get
private[sbt] trait Properties {
private[sbt] trait Properties:
def isAnsiSupported: Boolean
def isColorEnabled: Boolean
def out: ConsoleOut
}
private[sbt] object Properties {
private[sbt] object Properties:
def from(terminal: Terminal): Properties =
from(ConsoleOut.terminalOut(terminal), terminal.isAnsiSupported, terminal.isColorEnabled)
def safelyFrom(terminal: Terminal): Properties =
from(ConsoleOut.safeTerminalOut(terminal), terminal.isAnsiSupported, terminal.isColorEnabled)
def from(o: ConsoleOut, ansi: Boolean, color: Boolean): Properties = new Properties {
def from(o: ConsoleOut, ansi: Boolean, color: Boolean): Properties = new Properties:
override def isAnsiSupported: Boolean = ansi
override def isColorEnabled: Boolean = color
override def out = o
}
}
/** Hide stack trace altogether. */
val noSuppressedMessage = (_: SuppressedTraceContext) => None
@@ -139,11 +132,10 @@ object ConsoleAppender {
@deprecated("Use Terminal.isAnsiSupported or Terminal.isColorEnabled", "1.4.0")
lazy val formatEnabledInEnv: Boolean = Terminal.isAnsiSupported
private[sbt] def parseLogOption(s: String): LogOption = Terminal.parseLogOption(s) match {
private[sbt] def parseLogOption(s: String): LogOption = Terminal.parseLogOption(s) match
case Some(true) => LogOption.Always
case Some(false) => LogOption.Never
case _ => LogOption.Auto
}
private val generateId: AtomicInteger = new AtomicInteger
@@ -223,10 +215,9 @@ object ConsoleAppender {
name: String,
out: ConsoleOut,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender = {
): Appender =
val ansi = Terminal.isAnsiSupported
apply(name, out, ansi, ansi, suppressedMessage)
}
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
@@ -253,9 +244,8 @@ object ConsoleAppender {
* @return
* A new `ConsoleAppender` that writes to `out`.
*/
def apply(name: String, terminal: Terminal): Appender = {
def apply(name: String, terminal: Terminal): Appender =
new ConsoleAppender(name, Properties.from(terminal), noSuppressedMessage)
}
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `terminal`.
@@ -265,9 +255,8 @@ object ConsoleAppender {
* @param terminal The terminal to which this appender corresponds
* @return A new `ConsoleAppender` that writes to `terminal`.
*/
def safe(name: String, terminal: Terminal): Appender = {
def safe(name: String, terminal: Terminal): Appender =
new ConsoleAppender(name, Properties.safelyFrom(terminal), noSuppressedMessage)
}
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
@@ -285,9 +274,8 @@ object ConsoleAppender {
name: String,
terminal: Terminal,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender = {
): Appender =
new ConsoleAppender(name, Properties.from(terminal), suppressedMessage)
}
/**
* A new `ConsoleAppender` identified by `name`, and that writes to `out`.
@@ -309,16 +297,15 @@ object ConsoleAppender {
ansiCodesSupported: Boolean,
useFormat: Boolean,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender = {
): Appender =
new ConsoleAppender(
name,
Properties.from(out, ansiCodesSupported, useFormat),
suppressedMessage
)
}
private[sbt] def generateName(): String = "out-" + generateId.incrementAndGet
}
end ConsoleAppender
// See http://stackoverflow.com/questions/24205093/how-to-create-a-custom-appender-in-log4j2
// for custom appender using Java.
@@ -334,10 +321,9 @@ class ConsoleAppender(
override private[sbt] val name: String,
override private[sbt] val properties: Properties,
override private[sbt] val suppressedMessage: SuppressedTraceContext => Option[String]
) extends Appender {
) extends Appender:
override def close(): Unit = ()
}
trait Appender extends AutoCloseable {
trait Appender extends AutoCloseable:
private[sbt] def name: String
private[sbt] def properties: Properties
private[sbt] def suppressedMessage: SuppressedTraceContext => Option[String]
@@ -373,15 +359,11 @@ trait Appender extends AutoCloseable {
* @param traceLevel
* How to shorten the stack trace.
*/
def trace(t: => Throwable, traceLevel: Int): Unit = {
if (traceLevel >= 0)
write(StackTrace.trimmed(t, traceLevel))
if (traceLevel <= 2) {
def trace(t: => Throwable, traceLevel: Int): Unit =
if traceLevel >= 0 then write(StackTrace.trimmed(t, traceLevel))
if traceLevel <= 2 then
val ctx = new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
for (msg <- suppressedMessage(ctx))
appendLog(NO_COLOR, "trace", NO_COLOR, msg)
}
}
for msg <- suppressedMessage(ctx) do appendLog(NO_COLOR, "trace", NO_COLOR, msg)
/**
* Logs a `ControlEvent` to the log.
@@ -402,9 +384,8 @@ trait Appender extends AutoCloseable {
* @param message
* The message to log.
*/
def appendLog(level: Level.Value, message: => String): Unit = {
def appendLog(level: Level.Value, message: => String): Unit =
appendLog(labelColor(level), level.toString, NO_COLOR, message)
}
/**
* Select the right color for the label given `level`.
@@ -415,11 +396,10 @@ trait Appender extends AutoCloseable {
* The color to use to color the label.
*/
private def labelColor(level: Level.Value): String =
level match {
level match
case Level.Error => RED
case Level.Warn => YELLOW
case _ => NO_COLOR
}
/**
* Appends a full message to the log. Each line is prefixed with `[$label]`, written in
@@ -441,81 +421,71 @@ trait Appender extends AutoCloseable {
messageColor: String,
message: String
): Unit =
try {
try
// according to https://github.com/sbt/sbt/issues/5608, sometimes we get a null message
if (message == null) ()
else {
if message == null then ()
else
val len = labelColor.length + label.length + messageColor.length + reset.length * 3
val builder: StringBuilder = new StringBuilder(len)
message.linesIterator.foreach { line =>
builder.ensureCapacity(len + line.length + 4)
builder.setLength(0)
def fmted(a: String, b: String) = {
if (useFormat) builder.append(reset).append(a).append(b).append(reset)
def fmted(a: String, b: String) =
if useFormat then builder.append(reset).append(a).append(b).append(reset)
else builder.append(b)
}
if (useFormat) builder.append(reset)
if useFormat then builder.append(reset)
builder.append('[')
fmted(labelColor, label)
builder.append("] ")
fmted(messageColor, line)
write(builder.toString)
}
}
} catch { case _: InterruptedException => }
catch
case _: InterruptedException =>
// success is called by ConsoleLogger.
private[sbt] def success(message: => String): Unit = {
private[sbt] def success(message: => String): Unit =
appendLog(SUCCESS_LABEL_COLOR, Level.SuccessLabel, SUCCESS_MESSAGE_COLOR, message)
}
private def write(msg: String): Unit = {
private def write(msg: String): Unit =
// There is no api for removing only colors but not other ansi escape sequences
// so we do nothing if useFormat is false but ansiCodesSupported is true which is
// a rare use case but if ansiCodesSupported is true, color codes should work so
// the output may have unwanted colors but it would still be legible. This should
// only be relevant if the log message string itself contains ansi escape sequences
// other than color codes which is very unlikely.
val toWrite = if ((!ansiCodesSupported || !useFormat) && msg.getBytes.contains(27.toByte)) {
val toWrite = if (!ansiCodesSupported || !useFormat) && msg.getBytes.contains(27.toByte) then
val (bytes, len) =
EscHelpers.strip(msg.getBytes, stripAnsi = !ansiCodesSupported, stripColor = !useFormat)
new String(bytes, 0, len)
} else msg
else msg
out.println(toWrite)
}
private def appendTraceEvent(te: TraceEvent): Unit = {
private def appendTraceEvent(te: TraceEvent): Unit =
val traceLevel = getTrace
if (traceLevel >= 0) {
if traceLevel >= 0 then
val throwableShowLines: ShowLines[Throwable] =
ShowLines[Throwable]((t: Throwable) => {
List(StackTrace.trimmed(t, traceLevel))
})
ShowLines[Throwable]((t: Throwable) => List(StackTrace.trimmed(t, traceLevel)))
val codec: ShowLines[TraceEvent] =
ShowLines[TraceEvent]((t: TraceEvent) => {
throwableShowLines.showLines(t.message)
})
ShowLines[TraceEvent]((t: TraceEvent) => throwableShowLines.showLines(t.message))
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
}
if (traceLevel <= 2) {
if traceLevel <= 2 then
suppressedMessage(
new SuppressedTraceContext(traceLevel, ansiCodesSupported && useFormat)
) foreach {
appendLog(Level.Error, _)
}
}
}
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
def appendEvent(oe: ObjectEvent[?]): Unit = {
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit =
def appendEvent(oe: ObjectEvent[?]): Unit =
val contentType = oe.contentType
contentType match {
contentType match
case "sbt.internal.util.TraceEvent" => appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
case "sbt.internal.util.ProgressEvent" =>
case _ =>
LogExchange.stringCodec[AnyRef](contentType) match {
LogExchange.stringCodec[AnyRef](contentType) match
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
case Some(codec) =>
@@ -524,19 +494,14 @@ trait Appender extends AutoCloseable {
_
))
case _ => appendLog(level, oe.message.toString)
}
}
}
o match {
o match
case x: StringEvent => Vector(x.message) foreach { appendLog(level, _) }
case x: ObjectEvent[?] => appendEvent(x)
case _ => Vector(o.toString) foreach { appendLog(level, _) }
}
}
end appendMessageContent
private[sbt] def appendObjectEvent[T](level: Level.Value, message: => ObjectEvent[T]): Unit =
appendMessageContent(level, message)
}
end Appender
final class SuppressedTraceContext(val traceLevel: Int, val useFormat: Boolean)
@@ -13,27 +13,25 @@ import java.nio.channels.ClosedChannelException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicReference
sealed trait ConsoleOut {
sealed trait ConsoleOut:
val lockObject: AnyRef
def print(s: String): Unit
def println(s: String): Unit
def println(): Unit
def flush(): Unit
}
object ConsoleOut {
object ConsoleOut:
def systemOut: ConsoleOut = terminalOut
private[sbt] object NullConsoleOut extends ConsoleOut {
private[sbt] object NullConsoleOut extends ConsoleOut:
override val lockObject: AnyRef = this
override def print(s: String): Unit = {}
override def println(): Unit = {}
override def println(s: String): Unit = {}
override def flush(): Unit = {}
}
private[sbt] def globalProxy: ConsoleOut = Proxy
private[sbt] def setGlobalProxy(out: ConsoleOut): Unit = Proxy.set(out)
private[sbt] def getGlobalProxy: ConsoleOut = Proxy.proxy.get
private object Proxy extends ConsoleOut {
private object Proxy extends ConsoleOut:
private[ConsoleOut] val proxy = new AtomicReference[ConsoleOut](systemOut)
private def get: ConsoleOut = proxy.get
def set(proxy: ConsoleOut): Unit = this.proxy.set(proxy)
@@ -43,7 +41,6 @@ object ConsoleOut {
override def println(): Unit = get.println()
override def flush(): Unit = get.flush()
override def toString: String = s"ProxyConsoleOut"
}
def overwriteContaining(s: String): (String, String) => Boolean =
(cur, prev) => cur.contains(s) && prev.contains(s)
@@ -58,7 +55,7 @@ object ConsoleOut {
* 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 {
def systemOutOverwrite(f: (String, String) => Boolean): ConsoleOut = new ConsoleOut:
val lockObject: PrintStream = System.out
private var last: Option[String] = None
private val current = new java.lang.StringBuffer
@@ -66,7 +63,7 @@ object ConsoleOut {
def println(s: String): Unit = synchronized { current.append(s); println() }
def println(): Unit = synchronized {
val s = current.toString
if (Terminal.isAnsiSupported && last.exists(lmsg => f(s, lmsg)))
if Terminal.isAnsiSupported && last.exists(lmsg => f(s, lmsg)) then
lockObject.print(OverwriteLine)
lockObject.println(s)
last = Some(s)
@@ -74,82 +71,74 @@ object ConsoleOut {
}
def flush(): Unit = synchronized {
val s = current.toString
if (Terminal.isAnsiSupported && last.exists(lmsg => f(s, lmsg)))
if Terminal.isAnsiSupported && last.exists(lmsg => f(s, lmsg)) then
lockObject.print(OverwriteLine)
lockObject.print(s)
last = Some(s)
current.setLength(0)
}
override def toString: String = s"SystemOutOverwrite@${System.identityHashCode(this)}"
}
def terminalOut: ConsoleOut = new ConsoleOut {
def terminalOut: ConsoleOut = new ConsoleOut:
override val lockObject: AnyRef = System.out
override def print(s: String): Unit = Terminal.get.printStream.print(s)
override def println(s: String): Unit = Terminal.get.printStream.println(s)
override def println(): Unit = Terminal.get.printStream.println()
override def flush(): Unit = Terminal.get.printStream.flush()
override def toString: String = s"TerminalOut"
}
/**
* Same as terminalOut but it catches and ignores the ClosedChannelException
*/
def safeTerminalOut(terminal: Terminal): ConsoleOut = {
def safeTerminalOut(terminal: Terminal): ConsoleOut =
val out = terminalOut(terminal)
new ConsoleOut {
new ConsoleOut:
override val lockObject: AnyRef = terminal
override def print(s: String): Unit = catchException(out.print(s))
override def println(s: String): Unit = catchException(out.println(s))
override def println(): Unit = catchException(out.println())
override def flush(): Unit = catchException(out.flush())
override def toString: String = s"SafeTerminalOut($terminal)"
private def catchException(f: => Unit): Unit = {
private def catchException(f: => Unit): Unit =
try f
catch {
case _: ClosedChannelException => ()
}
}
}
}
catch case _: ClosedChannelException => ()
private val consoleOutPerTerminal = new ConcurrentHashMap[Terminal, ConsoleOut]
def terminalOut(terminal: Terminal): ConsoleOut = consoleOutPerTerminal.get(terminal) match {
def terminalOut(terminal: Terminal): ConsoleOut = consoleOutPerTerminal.get(terminal) match
case null =>
val res = new ConsoleOut {
val res = new ConsoleOut:
override val lockObject: AnyRef = terminal
override def print(s: String): Unit = terminal.printStream.print(s)
override def println(s: String): Unit = terminal.printStream.println(s)
override def println(): Unit = terminal.printStream.println()
override def flush(): Unit = terminal.printStream.flush()
override def toString: String = s"TerminalOut($terminal)"
}
consoleOutPerTerminal.put(terminal, res)
res
case c => c
}
def printStreamOut(out: PrintStream): ConsoleOut = new ConsoleOut {
def printStreamOut(out: PrintStream): ConsoleOut = new ConsoleOut:
val lockObject: AnyRef = out
def print(s: String) = out.print(s)
def println(s: String) = out.println(s)
def println() = out.println()
def flush() = out.flush()
override def toString: String = s"PrintStreamConsoleOut($out)"
}
def printWriterOut(out: PrintWriter): ConsoleOut = new ConsoleOut {
def printWriterOut(out: PrintWriter): ConsoleOut = new ConsoleOut:
val lockObject: AnyRef = out
def print(s: String) = out.print(s)
def println(s: String) = { out.println(s); flush() }
def println() = { out.println(); flush() }
def flush() = { out.flush() }
def println(s: String) =
out.println(s); flush()
def println() =
out.println(); flush()
def flush() = out.flush()
override def toString: String = s"PrintWriterConsoleOut($out)"
}
def bufferedWriterOut(out: BufferedWriter): ConsoleOut = new ConsoleOut {
def bufferedWriterOut(out: BufferedWriter): ConsoleOut = new ConsoleOut:
val lockObject: AnyRef = out
def print(s: String) = out.write(s)
def println(s: String) = { out.write(s); println() }
def println() = { out.newLine(); flush() }
def flush() = { out.flush() }
def println(s: String) =
out.write(s); println()
def println() =
out.newLine(); flush()
def flush() = out.flush()
override def toString: String = s"BufferedWriterConsoleOut($out)"
}
}
end ConsoleOut
@@ -12,7 +12,7 @@ import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
import scala.util.Try
object EscHelpers {
object EscHelpers:
/** Escape character, used to introduce an escape sequence. */
final val ESC = '\u001B'
@@ -64,43 +64,39 @@ object EscHelpers {
* isEscapeTerminator
*/
def removeEscapeSequences(s: String): String =
if (s.isEmpty || !hasEscapeSequence(s)) s
else {
if s.isEmpty || !hasEscapeSequence(s) then s
else
val sb = new java.lang.StringBuilder
nextESC(s, 0, sb)
sb.toString
}
@tailrec
private def nextESC(s: String, start: Int, sb: java.lang.StringBuilder): Unit = {
private def nextESC(s: String, start: Int, sb: java.lang.StringBuilder): Unit =
val escIndex = s.indexOf(ESC, start)
if (escIndex < 0) {
if escIndex < 0 then
sb.append(s, start, s.length)
()
} else {
else
sb.append(s, start, escIndex)
val next: Int =
if (escIndex + 1 >= s.length) skipESC(s, escIndex + 1)
if escIndex + 1 >= s.length then skipESC(s, escIndex + 1)
// If it's a CSI we skip past it and then look for a terminator.
else if (isCSI(s.charAt(escIndex + 1))) skipESC(s, escIndex + 2)
else if (isAnsiTwoCharacterTerminator(s.charAt(escIndex + 1))) escIndex + 2
else {
else if isCSI(s.charAt(escIndex + 1)) then skipESC(s, escIndex + 2)
else if isAnsiTwoCharacterTerminator(s.charAt(escIndex + 1)) then escIndex + 2
else
// There could be non-ANSI character sequences we should make sure we handle here.
skipESC(s, escIndex + 1)
}
nextESC(s, next, sb)
}
}
private val esc = 1
private val csi = 2
def cursorPosition(s: String): Int = {
def cursorPosition(s: String): Int =
val bytes = s.getBytes
var i = 0
var index = 0
var state = 0
val digit = new ArrayBuffer[Byte]
while (i < bytes.length) {
bytes(i) match {
while i < bytes.length do
bytes(i) match
case 27 => state = esc
case b if (state == esc || state == csi) && b >= 48 && b < 58 =>
state = csi
@@ -112,23 +108,22 @@ object EscHelpers {
case b if state == csi =>
val leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
state = 0
b.toChar match {
b.toChar match
case 'D' => index = math.max(index - leftDigit, 0)
case 'C' => index += leftDigit
case 'K' =>
case 'J' => if (leftDigit == 2) index = 0
case 'J' => if leftDigit == 2 then index = 0
case 'm' =>
case ';' => state = csi
case _ =>
}
digit.clear()
case _ =>
index += 1
}
end match
i += 1
}
end while
index
}
end cursorPosition
/**
* Strips ansi escape and color codes from an input string.
@@ -143,7 +138,7 @@ object EscHelpers {
* 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) = {
def strip(bytes: Array[Byte], stripAnsi: Boolean, stripColor: Boolean): (Array[Byte], Int) =
val res = new Array[Byte](bytes.length)
var index = 0
var state = 0
@@ -151,11 +146,11 @@ object EscHelpers {
val digit = new ArrayBuffer[Byte]
var escIndex = -1
bytes.foreach { b =>
if (index < res.length) res(index) = b
if index < res.length then res(index) = b
index += 1
limit = math.max(limit, index)
if (state == 0) escIndex = -1
b match {
if state == 0 then escIndex = -1
b match
case 27 =>
escIndex = index - 1
state = esc
@@ -169,20 +164,19 @@ object EscHelpers {
case b if state == csi =>
val leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
state = 0
b.toChar match {
b.toChar match
case 'h' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'J' | 'K' =>
if (stripAnsi) index = math.max(escIndex, 0)
case 'm' => if (stripColor) index = escIndex
if stripAnsi then index = math.max(escIndex, 0)
case 'm' => if stripColor then index = escIndex
case ';' | 's' | 'u' | '?' => state = csi
case b =>
}
digit.clear()
case b if state == esc => state = 0
case b =>
}
end match
}
(res, index)
}
end strip
/**
* Removes the ansi escape sequences from a string and makes a best attempt at calculating any
@@ -197,7 +191,7 @@ object EscHelpers {
* a string containing the original characters of the input stream with the ansi escape codes
* removed.
*/
def stripColorsAndMoves(s: String): String = {
def stripColorsAndMoves(s: String): String =
val bytes = s.getBytes
val res = new Array[Byte](bytes.length)
var index = 0
@@ -216,17 +210,16 @@ object EscHelpers {
case b if state == csi =>
val leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
state = 0
b.toChar match {
b.toChar match
case 'h' => index = math.max(index - 1, 0)
case 'D' => index = math.max(index - leftDigit, 0)
case 'C' => index = math.min(limit, math.min(index + leftDigit, res.length - 1))
case 'K' | 'J' =>
if (leftDigit > 0) (0 until index).foreach(res(_) = 32)
if leftDigit > 0 then (0 until index).foreach(res(_) = 32)
else res(index) = 32
case 'm' =>
case ';' => state = csi
case b => state = csi
}
digit.clear()
case b if state == esc => state = 0
case b =>
@@ -235,21 +228,15 @@ object EscHelpers {
limit = math.max(limit, index)
}
new String(res, 0, limit)
}
end stripColorsAndMoves
/**
* Skips the escape sequence starting at `i-1`. `i` should be positioned at the character after
* the ESC that starts the sequence.
*/
@tailrec
private def skipESC(s: String, i: Int): Int = {
if (i >= s.length) {
i
} else if (isEscapeTerminator(s.charAt(i))) {
i + 1
} else {
skipESC(s, i + 1)
}
}
}
private def skipESC(s: String, i: Int): Int =
if i >= s.length then i
else if isEscapeTerminator(s.charAt(i)) then i + 1
else skipESC(s, i + 1)
end EscHelpers
@@ -15,26 +15,17 @@ import sbt.util.*
* 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 {
def trace(t: => Throwable): Unit = {
if (traceEnabled)
delegate.trace(t)
}
class FilterLogger(delegate: AbstractLogger) extends BasicLogger:
def trace(t: => Throwable): Unit =
if traceEnabled then delegate.trace(t)
override def setSuccessEnabled(flag: Boolean): Unit = delegate.setSuccessEnabled(flag)
override def successEnabled = delegate.successEnabled
override def setTrace(level: Int): Unit = delegate.setTrace(level)
override def getTrace = delegate.getTrace
def log(level: Level.Value, message: => String): Unit = {
if (atLevel(level))
delegate.log(level, message)
}
def success(message: => String): Unit = {
if (successEnabled)
delegate.success(message)
}
def control(event: ControlEvent.Value, message: => String): Unit = {
if (atLevel(Level.Info))
delegate.control(event, message)
}
def log(level: Level.Value, message: => String): Unit =
if atLevel(level) then delegate.log(level, message)
def success(message: => String): Unit =
if successEnabled then delegate.success(message)
def control(event: ControlEvent.Value, message: => String): Unit =
if atLevel(Level.Info) then delegate.control(event, message)
def logAll(events: Seq[LogEvent]): Unit = delegate.logAll(events)
}
@@ -11,26 +11,18 @@ package sbt.internal.util
import sbt.util.*
/** Promotes the simple Logger interface to the full AbstractLogger interface. */
class FullLogger(delegate: Logger) extends BasicLogger {
def trace(t: => Throwable): Unit = {
if (traceEnabled)
delegate.trace(t)
}
def log(level: Level.Value, message: => String): Unit = {
if (atLevel(level))
delegate.log(level, message)
}
class FullLogger(delegate: Logger) extends BasicLogger:
def trace(t: => Throwable): Unit =
if traceEnabled then delegate.trace(t)
def log(level: Level.Value, message: => String): Unit =
if atLevel(level) then delegate.log(level, message)
def success(message: => String): Unit =
if (successEnabled)
delegate.success(message)
if successEnabled then delegate.success(message)
def control(event: ControlEvent.Value, message: => String): Unit =
info(message)
def logAll(events: Seq[LogEvent]): Unit = events.foreach(log)
}
object FullLogger {
object FullLogger:
def apply(delegate: Logger): AbstractLogger =
delegate match {
delegate match
case d: AbstractLogger => d
case _ => new FullLogger(delegate)
}
}
@@ -41,7 +41,7 @@ final case class GlobalLogging1(
* 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) {
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)
@@ -58,14 +58,11 @@ final case class GlobalLogBacking(file: File, last: Option[File], newBackingFile
*/
def unshift = GlobalLogBacking(last getOrElse file, None, newBackingFile)
}
object GlobalLogBacking {
object GlobalLogBacking:
def apply(newBackingFile: => File): GlobalLogBacking =
GlobalLogBacking(newBackingFile, None, () => newBackingFile)
}
object GlobalLogging {
object GlobalLogging:
import java.util.concurrent.atomic.AtomicInteger
private def generateName: String = "GlobalLogging" + generateId.incrementAndGet
@@ -75,10 +72,9 @@ object GlobalLogging {
newLogger: (PrintWriter, GlobalLogBacking) => GlobalLogging1,
newBackingFile: => File,
console: ConsoleOut
): GlobalLogging1 = {
): GlobalLogging1 =
val log = ConsoleLogger(console)
GlobalLogging1(log, console, log, GlobalLogBacking(newBackingFile), newLogger)
}
def initial(
newAppender: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging,
@@ -92,11 +88,10 @@ object GlobalLogging {
newBackingFile: => File,
console: ConsoleOut,
initialLevel: Level.Value = Level.Info
): GlobalLogging = {
): GlobalLogging =
val loggerName = generateName
val log = LoggerContext.globalContext.logger(loggerName, None, None)
val appender = ConsoleAppender(ConsoleAppender.generateName(), console)
LoggerContext.globalContext.addAppender(loggerName, appender -> initialLevel)
GlobalLogging(log, console, appender, GlobalLogBacking(newBackingFile), newAppender)
}
}
end GlobalLogging
@@ -24,159 +24,135 @@ import sbt.internal.util.Terminal.hasConsole
import scala.jdk.CollectionConverters.*
import java.util.concurrent.LinkedBlockingQueue
private[sbt] object JLine3 {
private[sbt] object JLine3:
private[util] val initialAttributes = new AtomicReference[Attributes]
private[util] def system: org.jline.terminal.Terminal = {
private[util] def system: org.jline.terminal.Terminal =
val term =
org.jline.terminal.TerminalBuilder
.builder()
.system(hasConsole)
.paused(true)
.build()
initialAttributes.get match {
initialAttributes.get match
case null => initialAttributes.set(term.getAttributes)
case _ =>
}
term
}
private[sbt] def apply(term: Terminal): JTerminal = {
if (System.getProperty("jline.terminal", "") == "none")
private[sbt] def apply(term: Terminal): JTerminal =
if System.getProperty("jline.terminal", "") == "none" then
new DumbTerminal(term.inputStream, term.outputStream)
else wrapTerminal(term)
}
private[util] def decodeInput(decoder: CharsetDecoder, inputStream: InputStream): Int = {
private[util] def decodeInput(decoder: CharsetDecoder, inputStream: InputStream): Int =
val bytes = new Array[Byte](4)
var i = 0
var res = -2
while (i < 4 && res == -2) {
inputStream.read() match {
while i < 4 && res == -2 do
inputStream.read() match
case -1 => res = -1
case byte =>
bytes(i) = byte.toByte
i += 1
val bb = ByteBuffer.wrap(bytes, 0, i)
try {
try
val cb = decoder.decode(bb)
val it = cb.codePoints().iterator
if (it.hasNext) res = it.next
} catch { case _: CharacterCodingException => }
}
}
if it.hasNext then res = it.next
catch
case _: CharacterCodingException =>
res
}
private def wrapTerminal(term: Terminal): JTerminal = {
private def wrapTerminal(term: Terminal): JTerminal =
new AbstractTerminal(
term.name,
"nocapabilities",
Charset.forName("UTF-8"),
SignalHandler.SIG_DFL
) {
):
val closed = new AtomicBoolean(false)
setOnClose { () =>
doClose()
reader.close()
if (closed.compareAndSet(false, true)) {
if closed.compareAndSet(false, true) then
// This is necessary to shutdown the non blocking input reader
// so that it doesn't keep blocking
term.inputStream match {
term.inputStream match
case w: Terminal.WriteableInputStream => w.cancel()
case _ =>
}
}
}
// returns 'null' if the terminal was created with no provider
override def getProvider(): TerminalProvider = null
// returns 'null' if the terminal is not bound to a system stream.
override def getSystemStream(): SystemStream = null
override val input: InputStream = new InputStream {
override def read: Int = {
val res = term.inputStream match {
override val input: InputStream = new InputStream:
override def read: Int =
val res = term.inputStream match
case w: Terminal.WriteableInputStream =>
val result = new LinkedBlockingQueue[Integer]
try {
try
w.read(result)
result.poll match {
result.poll match
case null => throw new ClosedException
case i => i.toInt
}
} catch {
catch
case _: InterruptedException =>
w.cancel()
throw new ClosedException
}
case _ => throw new ClosedException
}
res match {
res match
case 3 /* ctrl+c */ => throw new ClosedException
case r => r
}
}
}
override val output: OutputStream = new OutputStream {
override val output: OutputStream = new OutputStream:
override def write(b: Int): Unit = write(Array[Byte](b.toByte))
override def write(b: Array[Byte]): Unit = if (!closed.get) term.withPrintStream { ps =>
ps.write(b)
term.prompt match {
case a: Prompt.AskUser => a.write(b)
case _ =>
override def write(b: Array[Byte]): Unit = if !closed.get then
term.withPrintStream { ps =>
ps.write(b)
term.prompt match
case a: Prompt.AskUser => a.write(b)
case _ =>
}
}
override def write(b: Array[Byte], offset: Int, len: Int) =
write(Arrays.copyOfRange(b, offset, offset + len))
override def flush(): Unit = term.withPrintStream(_.flush())
}
override val reader = new NonBlockingReader {
override val reader = new NonBlockingReader:
val buffer = new LinkedBlockingQueue[Integer]
val thread = new AtomicReference[Thread]
private def fillBuffer(): Unit = thread.synchronized {
thread.set(Thread.currentThread)
try buffer.put(decodeInput(encoding.newDecoder, term.inputStream))
catch { case _: InterruptedException => buffer.put(-3) }
catch case _: InterruptedException => buffer.put(-3)
}
override def close(): Unit = thread.get match {
override def close(): Unit = thread.get match
case null =>
case t => t.interrupt()
}
override def read(timeout: Long, peek: Boolean) = {
if (buffer.isEmpty && !peek) fillBuffer()
(if (peek) buffer.peek else buffer.take) match {
override def read(timeout: Long, peek: Boolean) =
if buffer.isEmpty && !peek then fillBuffer()
(if peek then buffer.peek else buffer.take) match
case null => -2
case i => if (i == -3) throw new InterruptedException else i
}
}
override def peek(timeout: Long): Int = buffer.peek() match {
case i => if i == -3 then throw new InterruptedException else i
override def peek(timeout: Long): Int = buffer.peek() match
case null => -1
case i => i.toInt
}
override def readBuffered(buf: Array[Char], off: Int, len: Int, timeout: Long): Int = {
if (buffer.isEmpty) fillBuffer()
buffer.take match {
override def readBuffered(buf: Array[Char], off: Int, len: Int, timeout: Long): Int =
if buffer.isEmpty then fillBuffer()
buffer.take match
case i if i == -1 => -1
case i =>
buf(0) = i.toChar
1
}
}
}
override val writer: PrintWriter = new PrintWriter(output, true)
/*
* For now assume that the terminal capabilities for client and server
* are the same.
*/
override def getStringCapability(cap: Capability): String = {
term.getStringCapability(cap.toString) match {
override def getStringCapability(cap: Capability): String =
term.getStringCapability(cap.toString) match
case null if cap == Capability.key_dc && Util.isWindows => "\\E[3~"
case null if cap == Capability.key_end && Util.isWindows => "\\E[4~"
case null if cap == Capability.key_ic && Util.isWindows => "\\E[2~"
case c => c
}
}
override def getNumericCapability(cap: Capability): Integer = {
if (cap == Capability.max_colors && !term.isColorEnabled) 1
override def getNumericCapability(cap: Capability): Integer =
if cap == Capability.max_colors && !term.isColorEnabled then 1
else term.getNumericCapability(cap.toString)
}
override def getBooleanCapability(cap: Capability): Boolean =
term.getBooleanCapability(cap.toString)
def getAttributes(): Attributes = attributesFromMap(term.getAttributes)
@@ -186,19 +162,15 @@ private[sbt] object JLine3 {
): 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 = {
override def enterRawMode(): Attributes =
// don't actually modify the term, that is handled by LineReader
attributesFromMap(term.getAttributes)
}
}
}
// We need to set the ENABLE_PROCESS_INPUT flag for ctrl+c to be treated as a signal in windows
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
private val ENABLE_PROCESS_INPUT = 1
private[util] def setEnableProcessInput(): Unit = if (Util.isWindows) {
private[util] def setEnableProcessInput(): Unit = if Util.isWindows then
WindowsSupport.setConsoleMode(WindowsSupport.getConsoleMode | ENABLE_PROCESS_INPUT)
}
private[util] def enterRawMode(term: JTerminal): Unit = {
private[util] def enterRawMode(term: JTerminal): Unit =
val prevAttr = initialAttributes.get
val newAttr = new Attributes(prevAttr)
// These flags are copied from the jline3 enterRawMode but the jline implementation
@@ -207,15 +179,13 @@ private[sbt] object JLine3 {
newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false)
term.setAttributes(newAttr)
setEnableProcessInput()
}
private[util] def exitRawMode(term: JTerminal): Unit = {
private[util] def exitRawMode(term: JTerminal): Unit =
val initAttr = initialAttributes.get
val newAttr = new Attributes(initAttr)
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO), true)
term.setAttributes(newAttr)
setEnableProcessInput()
}
private[util] def toMap(jattributes: Attributes): Map[String, String] = {
private[util] def toMap(jattributes: Attributes): Map[String, String] =
val result = new java.util.LinkedHashMap[String, String]
result.put(
"iflag",
@@ -242,7 +212,7 @@ private[sbt] object JLine3 {
.mkString(" ")
)
result.asScala.toMap
}
end toMap
private val iflagMap: Map[String, InputFlag] =
InputFlag.values.map(f => f.name.toLowerCase -> f).toMap
private val oflagMap: Map[String, Attributes.OutputFlag] =
@@ -253,14 +223,13 @@ private[sbt] object JLine3 {
LocalFlag.values.map(f => f.name.toLowerCase -> f).toMap
private val charMap: Map[String, Attributes.ControlChar] =
Attributes.ControlChar.values().map(f => f.name.toLowerCase -> f).toMap
private[sbt] def setMode(term: Terminal, canonical: Boolean, echo: Boolean): Unit = {
private[sbt] def setMode(term: Terminal, canonical: Boolean, echo: Boolean): Unit =
val prev = attributesFromMap(term.getAttributes)
val newAttrs = new Attributes(prev)
newAttrs.setLocalFlag(LocalFlag.ICANON, canonical)
newAttrs.setLocalFlag(LocalFlag.ECHO, echo)
term.setAttributes(toMap(newAttrs))
}
private[util] def attributesFromMap(map: Map[String, String]): Attributes = {
private[util] def attributesFromMap(map: Map[String, String]): Attributes =
val attributes = new Attributes
map.get("iflag").foreach { flags =>
flags.split(" ").foreach(f => iflagMap.get(f).foreach(attributes.setInputFlag(_, true)))
@@ -276,16 +245,14 @@ private[sbt] object JLine3 {
}
map.get("cchars").foreach { chars =>
chars.split(" ").foreach { keyValue =>
keyValue.split(",") match {
keyValue.split(",") match
case Array(k, v) =>
v.toIntOption.foreach(i => charMap.get(k).foreach(c => attributes.setControlChar(c, i)))
case _ =>
}
}
}
attributes
}
private[sbt] def isEchoEnabled(map: Map[String, String]): Boolean = {
end attributesFromMap
private[sbt] def isEchoEnabled(map: Map[String, String]): Boolean =
attributesFromMap(map).getLocalFlag(LocalFlag.ECHO)
}
}
end JLine3
@@ -20,7 +20,7 @@ class LoggerWriter(
delegate: Logger,
unbufferedLevel: Option[Level.Value],
nl: String = System.getProperty("line.separator")
) extends java.io.Writer {
) extends java.io.Writer:
def this(delegate: Logger, level: Level.Value) = this(delegate, Some(level))
def this(delegate: Logger) = this(delegate, None)
@@ -31,16 +31,14 @@ class LoggerWriter(
override def flush(): Unit =
synchronized {
if (buffer.nonEmpty) {
if buffer.nonEmpty then
log(buffer.toString)
buffer.clear()
}
}
def flushLines(level: Level.Value): Unit =
synchronized {
for (line <- lines)
delegate.log(level, line)
for line <- lines do delegate.log(level, line)
lines.clear()
}
@@ -51,18 +49,15 @@ class LoggerWriter(
}
@tailrec
private def process(): Unit = {
private def process(): Unit =
val i = buffer.indexOf(nl)
if (i >= 0) {
if i >= 0 then
log(buffer.substring(0, i))
buffer.delete(0, i + nl.length)
process()
}
}
private def log(s: String): Unit = unbufferedLevel match {
private def log(s: String): Unit = unbufferedLevel match
case None =>
lines += s; ()
case Some(level) => delegate.log(level, s)
}
}
end LoggerWriter
@@ -11,7 +11,7 @@ package sbt.internal.util
import sbt.util.*
import java.io.PrintWriter
object MainAppender {
object MainAppender:
import java.util.concurrent.atomic.AtomicInteger
private def generateGlobalBackingName: String =
"GlobalBacking" + generateId.incrementAndGet
@@ -21,36 +21,32 @@ object MainAppender {
log: ManagedLogger,
config: MainAppenderConfig,
context: LoggerContext
): ManagedLogger = {
): ManagedLogger =
import config.*
// TODO
// backed setTrace backingTrace
// multi: Logger
context.clearAppenders(log.name)
consoleOpt match {
consoleOpt match
case Some(a: ConsoleAppender) =>
a.setTrace(screenTrace)
context.addAppender(log.name, a -> screenLevel)
case _ =>
}
context.addAppender(log.name, backed -> backingLevel)
extra.foreach(a => context.addAppender(log.name, a -> Level.Info))
log
}
def globalDefault(
console: ConsoleOut
): (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging = {
): (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging =
lazy val newAppender
: (ManagedLogger, PrintWriter, GlobalLogBacking, LoggerContext) => GlobalLogging =
(log, writer, backing, lc) => {
(log, writer, backing, lc) =>
val backed: Appender = defaultBacked(generateGlobalBackingName)(writer)
val full = multiLogger(log, defaultMultiConfig(Option(console), backed, Nil), lc)
GlobalLogging(full, console, backed, backing, newAppender)
}
newAppender
}
def defaultMultiConfig(
consoleOpt: Option[ConsoleOut],
@@ -73,13 +69,12 @@ object MainAppender {
def defaultScreen(
console: ConsoleOut,
suppressedMessage: SuppressedTraceContext => Option[String]
): Appender = {
): Appender =
ConsoleAppender(
ConsoleAppender.generateName(),
console,
suppressedMessage = suppressedMessage
)
}
def defaultScreen(
name: String,
@@ -98,13 +93,12 @@ object MainAppender {
defaultBacked(generateGlobalBackingName, useFormat)
def defaultBacked(loggerName: String, useFormat: Boolean): PrintWriter => Appender =
to => {
to =>
ConsoleAppender(
ConsoleAppender.generateName(),
ConsoleOut.printWriterOut(to),
useFormat = useFormat
)
}
final case class MainAppenderConfig(
consoleOpt: Option[Appender],
@@ -115,4 +109,4 @@ object MainAppender {
screenTrace: Int,
backingTrace: Int
)
}
end MainAppender
@@ -13,10 +13,9 @@ import sbt.util.*
import sjsonnew.JsonFormat
import sbt.internal.util.appmacro.StringTypeTag
private[sbt] trait MiniLogger {
private[sbt] trait MiniLogger:
def log[T](level: Level.Value, message: ObjectEvent[T]): Unit
def log(level: Level.Value, message: => String): Unit
}
/**
* Delegates log events to the associated LogExchange.
@@ -28,7 +27,7 @@ class ManagedLogger(
xlogger: MiniLogger,
terminal: Option[Terminal],
private[sbt] val context: LoggerContext,
) extends Logger {
) extends Logger:
def this(
name: String,
channelName: Option[String],
@@ -42,15 +41,11 @@ class ManagedLogger(
xlogger.log(level, message)
// send special event for success since it's not a real log level
override def success(message: => String): Unit = {
if (terminal.fold(true)(_.isSuccessEnabled)) {
infoEvent[SuccessEvent](SuccessEvent(message))
}
}
override def success(message: => String): Unit =
if terminal.fold(true)(_.isSuccessEnabled) then infoEvent[SuccessEvent](SuccessEvent(message))
def registerStringCodec[A: ShowLines: StringTypeTag]: Unit = {
def registerStringCodec[A: ShowLines: StringTypeTag]: Unit =
LogExchange.registerStringCodec[A]
}
final def debugEvent[A: JsonFormat: StringTypeTag](event: => A): Unit =
logEvent(Level.Debug, event)
@@ -60,10 +55,9 @@ class ManagedLogger(
logEvent(Level.Error, event)
def logEvent[A: JsonFormat](level: Level.Value, event: => A)(using
tag: StringTypeTag[A]
): Unit = {
): Unit =
val v: A = event
// println("logEvent " + tag.key)
val entry: ObjectEvent[A] = ObjectEvent(level, v, channelName, execId, tag.key)
xlogger.log(level, entry)
}
}
end ManagedLogger
@@ -12,22 +12,19 @@ import sbt.util.*
// note that setting the logging level on this logger has no effect on its behavior, only
// on the behavior of the delegates.
class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger:
override def setLevel(newLevel: Level.Value): Unit = {
override def setLevel(newLevel: Level.Value): Unit =
super.setLevel(newLevel)
dispatch(new SetLevel(newLevel))
}
override def setTrace(level: Int): Unit = {
override def setTrace(level: Int): Unit =
super.setTrace(level)
dispatch(new SetTrace(level))
}
override def setSuccessEnabled(flag: Boolean): Unit = {
override def setSuccessEnabled(flag: Boolean): Unit =
super.setSuccessEnabled(flag)
dispatch(new SetSuccess(flag))
}
def trace(t: => Throwable): Unit = dispatch(new Trace(t))
def log(level: Level.Value, message: => String): Unit = dispatch(new Log(level, message))
@@ -37,9 +34,6 @@ class MultiLogger(delegates: List[AbstractLogger]) extends BasicLogger {
def control(event: ControlEvent.Value, message: => String): Unit =
delegates.foreach(_.control(event, message))
private def dispatch(event: LogEvent): Unit = {
for (d <- delegates) {
d.log(event)
}
}
}
private def dispatch(event: LogEvent): Unit =
for d <- delegates do d.log(event)
end MultiLogger
@@ -22,12 +22,11 @@ final class ObjectEvent[A](
val execId: Option[String],
val contentType: String,
val json: JValue
) extends Serializable {
) extends Serializable:
override def toString: String =
s"ObjectEvent($level, $message, $channelName, $execId, $contentType, $json)"
}
object ObjectEvent {
object ObjectEvent:
def apply[A: JsonFormat](
level: Level.Value,
message: A,
@@ -43,4 +42,3 @@ object ObjectEvent {
contentType,
Converter.toJsonUnsafe(message)
)
}
@@ -29,7 +29,7 @@ private[sbt] final class ProgressState(
val blankZone: Int,
val currentLineBytes: AtomicReference[ArrayBuffer[Byte]],
val maxItems: Int,
) {
):
def this(blankZone: Int, maxItems: Int) = this(
new AtomicReference(Nil),
new AtomicInteger(0),
@@ -42,50 +42,43 @@ private[sbt] final class ProgressState(
new String(currentLineBytes.get.toArray, "UTF-8").linesIterator.toSeq.lastOption
.map(EscHelpers.stripColorsAndMoves)
.filter(_.nonEmpty)
def reset(): Unit = {
def reset(): Unit =
progressLines.set(Nil)
padding.set(0)
currentLineBytes.set(new ArrayBuffer[Byte])
}
private val lineBuffer = new ArrayBlockingQueue[String](300)
private[util] def getLines: Seq[String] = lineBuffer.asScala.toVector
private def appendLine(line: String) = while (!lineBuffer.offer(line)) { lineBuffer.poll }
private[util] def clearBytes(): Unit = {
private def appendLine(line: String) = while !lineBuffer.offer(line) do lineBuffer.poll
private[util] def clearBytes(): Unit =
val pad = padding.get
if (currentLineBytes.get.isEmpty && pad > 0) padding.decrementAndGet()
if currentLineBytes.get.isEmpty && pad > 0 then padding.decrementAndGet()
currentLineBytes.set(new ArrayBuffer[Byte])
}
private val lineSeparatorBytes: Array[Byte] = System.lineSeparator.getBytes("UTF-8")
private[util] def addBytes(terminal: Terminal, bytes: Seq[Byte]): Unit = {
private[util] def addBytes(terminal: Terminal, bytes: Seq[Byte]): Unit =
val previous: ArrayBuffer[Byte] = currentLineBytes.get
val padding = this.padding.get
val prevLineCount = if (padding > 0) terminal.lineCount(new String(previous.toArray)) else 0
val prevLineCount = if padding > 0 then terminal.lineCount(new String(previous.toArray)) else 0
previous ++= bytes
if (padding > 0) {
if padding > 0 then
val newLineCount = terminal.lineCount(new String(previous.toArray))
val diff = newLineCount - prevLineCount
this.padding.set(math.max(padding - diff, 0))
}
val lines = new String(previous.toArray, "UTF-8")
if (lines.contains(System.lineSeparator)) {
if lines.contains(System.lineSeparator) then
currentLineBytes.set(new ArrayBuffer[Byte])
if (!lines.endsWith(System.lineSeparator)) {
if !lines.endsWith(System.lineSeparator) then
val allLines = lines.split(System.lineSeparator)
allLines.dropRight(1).foreach(appendLine)
allLines.lastOption.foreach(currentLineBytes.get ++= _.getBytes("UTF-8"))
} else if (lines.contains(System.lineSeparator)) {
else if lines.contains(System.lineSeparator) then
lines.split(System.lineSeparator).foreach(appendLine)
}
}
}
private[util] def getPrompt(terminal: Terminal): Array[Byte] = {
if (terminal.prompt.isInstanceOf[Prompt.AskUser]) {
val prefix = if (terminal.isAnsiSupported) s"$DeleteLine$CursorLeft1000" else ""
private[util] def getPrompt(terminal: Terminal): Array[Byte] =
if terminal.prompt.isInstanceOf[Prompt.AskUser] then
val prefix = if terminal.isAnsiSupported then s"$DeleteLine$CursorLeft1000" else ""
prefix.getBytes ++ terminal.prompt.render().getBytes("UTF-8")
} else Array.empty
}
else Array.empty
private val cleanPrompt =
(DeleteLine + ClearScreenAfterCursor + CursorLeft1000).getBytes("UTF-8")
private val clearScreenBytes = ClearScreenAfterCursor.getBytes("UTF-8")
@@ -94,67 +87,57 @@ private[sbt] final class ProgressState(
bytes: Array[Byte],
printStream: PrintStream,
hasProgress: Boolean
): Unit = {
if (hasProgress) {
): Unit =
if hasProgress then
val canClearPrompt = currentLineBytes.get.isEmpty
addBytes(terminal, bytes.toSeq)
val toWrite = new ArrayBuffer[Byte]
terminal.prompt match {
terminal.prompt match
case a: Prompt.AskUser if a.render().nonEmpty && canClearPrompt => toWrite ++= cleanPrompt
case _ =>
}
val endsWithNewLine = bytes.endsWith(lineSeparatorBytes)
if (endsWithNewLine || bytes.containsSlice(lineSeparatorBytes)) {
if endsWithNewLine || bytes.containsSlice(lineSeparatorBytes) then
val parts = new String(bytes, "UTF-8").split(System.lineSeparator)
def appendLine(l: String, appendNewline: Boolean): Unit = {
def appendLine(l: String, appendNewline: Boolean): Unit =
toWrite ++= l.getBytes("UTF-8")
if (!l.getBytes("UTF-8").endsWith("\r".getBytes)) toWrite ++= clearScreenBytes
if (appendNewline) toWrite ++= lineSeparatorBytes
}
if !l.getBytes("UTF-8").endsWith("\r".getBytes) then toWrite ++= clearScreenBytes
if appendNewline then toWrite ++= lineSeparatorBytes
parts.dropRight(1).foreach(appendLine(_, true))
parts.lastOption match {
parts.lastOption match
case Some(l) => appendLine(l, bytes.endsWith(lineSeparatorBytes))
case None => toWrite ++= lineSeparatorBytes
}
} else toWrite ++= bytes
else toWrite ++= bytes
toWrite ++= clearScreenBytes
if (endsWithNewLine) {
if (progressLines.get.nonEmpty) {
val lastLine = terminal.prompt match {
if endsWithNewLine then
if progressLines.get.nonEmpty then
val lastLine = terminal.prompt match
case a: Prompt.AskUser => a.render()
case _ => currentLine.getOrElse("")
}
val lines = printProgress(terminal, lastLine)
toWrite ++= lines.getBytes("UTF-8")
}
toWrite ++= getPrompt(terminal)
}
printStream.write(toWrite.toArray)
printStream.flush()
} else printStream.write(bytes)
}
else printStream.write(bytes)
private[util] def printProgress(terminal: Terminal, lastLine: String): String = {
private[util] def printProgress(terminal: Terminal, lastLine: String): String =
val previousLines = progressLines.get
if (previousLines.nonEmpty) {
if previousLines.nonEmpty then
val currentLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
val (height, width) = terminal.getLineHeightAndWidth(lastLine)
val offset = width > 0
val pad = math.max(padding.get - height, 0)
val start = (if (offset) s"\n$CursorLeft1000" else "")
val start = (if offset then s"\n$CursorLeft1000" else "")
val totalSize = currentLength + blankZone + pad
val blank = CursorLeft1000 + s"\n$DeleteLine" * (totalSize - currentLength)
val lines = previousLines.mkString(DeleteLine, s"\n$DeleteLine", s"\n$DeleteLine")
val resetCursorUp = cursorUp(totalSize + (if (offset) 1 else 0))
val resetCursorUp = cursorUp(totalSize + (if offset then 1 else 0))
val resetCursor = resetCursorUp + CursorLeft1000 + lastLine
start + blank + lines + resetCursor
} else {
ClearScreenAfterCursor
}
}
}
else ClearScreenAfterCursor
end ProgressState
private[sbt] object ProgressState {
private[sbt] object ProgressState:
private val SERVER_IS_RUNNING = "sbt server is running "
// the + 2 is for the quotation marks
private val SERVER_IS_RUNNING_LENGTH = SERVER_IS_RUNNING.length + 3
@@ -168,36 +151,35 @@ private[sbt] object ProgressState {
private[sbt] def updateProgressState(
pe: ProgressEvent,
terminal: Terminal
): Unit = {
): Unit =
val state = terminal.progressState
val isAskUser = terminal.prompt.isInstanceOf[Prompt.AskUser]
val isRunning = terminal.prompt == Prompt.Running
val isBatch = terminal.prompt == Prompt.Batch
val isWatch = terminal.prompt == Prompt.Watch
if (terminal.isSupershellEnabled) {
if terminal.isSupershellEnabled then
setShowProgress(true) // used by Zinc to not show "done compiling"
if (!pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch)) {
if !pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch) then
terminal.withPrintStream { ps =>
val commandFromThisTerminal = pe.channelName.fold(true)(_ == terminal.name)
val info = if (commandFromThisTerminal) {
val info = if commandFromThisTerminal then
val base = pe.items.map { item =>
val elapsed = item.elapsedMicros / 1000000L
s" | => ${item.name} ${elapsed}s"
}
val limit = state.maxItems
if (base.size > limit + 1)
if base.size > limit + 1 then
s" | ... (${base.size - limit} other tasks)" +: base.takeRight(limit)
else base
} else {
else
pe.command.toSeq.flatMap { cmd =>
val width = terminal.getWidth
val sanitized = if ((cmd.length + SERVER_IS_RUNNING_LENGTH) > width) {
val sanitized = if (cmd.length + SERVER_IS_RUNNING_LENGTH) > width then
cmd.take(width - 3 - SERVER_IS_RUNNING_LENGTH) + "..."
} else cmd
val tail = if (isWatch) Nil else "enter 'cancel' to stop evaluation" :: Nil
else cmd
val tail = if isWatch then Nil else "enter 'cancel' to stop evaluation" :: Nil
s"$SERVER_IS_RUNNING '$sanitized'" :: tail
}
}
val currentLength = info.foldLeft(0)(_ + terminal.lineCount(_))
val previousLines = state.progressLines.getAndSet(info)
@@ -205,21 +187,21 @@ private[sbt] object ProgressState {
val prevSize = prevLength + state.padding.get
val lastLine =
if (isAskUser) terminal.prompt.render() else terminal.getLastLine.getOrElse("")
if isAskUser then terminal.prompt.render() else terminal.getLastLine.getOrElse("")
state.padding.set(math.max(0, prevSize - currentLength))
val toWrite =
state.getPrompt(terminal) ++ state.printProgress(terminal, lastLine).getBytes("UTF-8")
ps.write(toWrite)
ps.flush()
}
} else if (state.progressLines.get.nonEmpty) {
else if state.progressLines.get.nonEmpty then
state.progressLines.set(Nil)
terminal.withPrintStream { ps =>
val lastLine = terminal.getLastLine.getOrElse("")
ps.print(lastLine + ClearScreenAfterCursor)
ps.flush()
}
}
}
}
}
end if
end if
end updateProgressState
end ProgressState
@@ -11,30 +11,25 @@ package sbt.internal.util
import java.util.concurrent.LinkedBlockingQueue
import scala.jdk.CollectionConverters.*
private[sbt] sealed trait Prompt {
private[sbt] sealed trait Prompt:
def mkPrompt: () => String
def render(): String
def reset(): Unit
}
private[sbt] object Prompt {
private[sbt] case class AskUser(override val mkPrompt: () => String) extends Prompt {
private[sbt] object Prompt:
private[sbt] case class AskUser(override val mkPrompt: () => String) extends Prompt:
private val bytes = new LinkedBlockingQueue[Byte]
def write(b: Array[Byte]): Unit = b.foreach(bytes.put)
override def render(): String = {
override def render(): String =
val res = new String(bytes.asScala.toArray, "UTF-8")
if (res.endsWith(System.lineSeparator)) "" else res
}
if res.endsWith(System.lineSeparator) then "" else res
override def reset(): Unit = bytes.clear()
}
private[sbt] trait NoPrompt extends Prompt {
private[sbt] trait NoPrompt extends Prompt:
override val mkPrompt: () => String = () => ""
override def render(): String = ""
override def reset(): Unit = {}
}
private[sbt] case object Running extends NoPrompt
private[sbt] case object Batch extends NoPrompt
private[sbt] case object Watch extends NoPrompt
private[sbt] case object Pending extends NoPrompt
private[sbt] case object NoPrompt extends NoPrompt
}
@@ -12,7 +12,7 @@ import sbt.io.IO
import scala.collection.mutable.ListBuffer
import java.util.{ IdentityHashMap, Collections }
object StackTrace {
object StackTrace:
def isSbtClass(name: String) = name.startsWith("sbt.") || name.startsWith("xsbt.")
/**
@@ -28,51 +28,47 @@ object StackTrace {
*
* See also ConsoleAppender where d <= 2 is treated specially by printing a prepared statement.
*/
def trimmedLines(t: Throwable, d: Int): List[String] = {
def trimmedLines(t: Throwable, d: Int): List[String] =
require(d >= 0)
val b = new ListBuffer[String]()
val seen: java.util.Set[Throwable] =
Collections.newSetFromMap(new IdentityHashMap[Throwable, java.lang.Boolean]())
def appendStackTrace(t: Throwable, first: Boolean): Unit = {
def appendStackTrace(t: Throwable, first: Boolean): Unit =
val include: StackTraceElement => Boolean =
if (d == 0) element => !isSbtClass(element.getClassName)
else {
if d == 0 then element => !isSbtClass(element.getClassName)
else
var count = d - 1
(_ => { count -= 1; count >= 0 })
}
(_ =>
count -= 1; count >= 0
)
def appendElement(e: StackTraceElement): Unit = {
def appendElement(e: StackTraceElement): Unit =
b.append("\tat " + e)
()
}
if (!first) b.append("Caused by: " + t.toString)
if !first then b.append("Caused by: " + t.toString)
else b.append(t.toString)
val els = t.getStackTrace()
var i = 0
while ((i < els.length) && include(els(i))) {
while (i < els.length) && include(els(i)) do
appendElement(els(i))
i += 1
}
}
end appendStackTrace
appendStackTrace(t, true)
seen.add(t)
var c = t
while (c.getCause() != null && !seen.contains(c.getCause())) {
while c.getCause() != null && !seen.contains(c.getCause()) do
c = c.getCause()
seen.add(c)
appendStackTrace(c, false)
}
if (c.getCause() != null && seen.contains(c.getCause())) {
if c.getCause() != null && seen.contains(c.getCause()) then
b.append("[CIRCULAR REFERENCE: " + c.getCause().toString + "]")
}
b.toList
}
end trimmedLines
/**
* Return a printable representation of the stack trace associated with t. Information about t and
@@ -87,4 +83,4 @@ object StackTrace {
*/
def trimmed(t: Throwable, d: Int): String =
trimmedLines(t, d).mkString(IO.Newline)
}
end StackTrace
@@ -20,7 +20,7 @@ import scala.concurrent.duration.*
import scala.util.Try
import scala.util.control.NonFatal
trait Terminal extends AutoCloseable {
trait Terminal extends AutoCloseable:
/**
* Gets the current width of the terminal. The implementation reads a property from the jline
@@ -154,12 +154,12 @@ trait Terminal extends AutoCloseable {
private[sbt] def setSize(width: Int, height: Int): Unit
private[sbt] def name: String
private[sbt] final def withRawInput[T](f: => T): T = {
private[sbt] final def withRawInput[T](f: => T): T =
enterRawMode()
try f
catch { case e: InterruptedIOException => throw new InterruptedException }
try
try f
catch case e: InterruptedIOException => throw new InterruptedException
finally exitRawMode()
}
private[sbt] def enterRawMode(): Unit
private[sbt] def exitRawMode(): Unit
private[sbt] def write(bytes: Int*): Unit
@@ -171,7 +171,7 @@ trait Terminal extends AutoCloseable {
private val promptHolder: AtomicReference[Prompt] = new AtomicReference(Prompt.Batch)
private[sbt] final def prompt: Prompt = promptHolder.get
private[sbt] final def setPrompt(newPrompt: Prompt): Unit =
if (prompt != Prompt.NoPrompt) promptHolder.set(newPrompt)
if prompt != Prompt.NoPrompt then promptHolder.set(newPrompt)
/**
* Returns the number of lines that the input string will cover given the current width of the
@@ -182,16 +182,14 @@ trait Terminal extends AutoCloseable {
* @return
* the number of lines that the line will cover on the terminal
*/
private[sbt] def lineCount(line: String): Int = {
private[sbt] def lineCount(line: String): Int =
val lines = EscHelpers.stripColorsAndMoves(line).split('\n')
val width = getWidth
def count(l: String): Int = {
def count(l: String): Int =
val len = l.length
if (width > 0 && len > 0) (len - 1 + width) / width else 0
}
if (lines.nonEmpty) lines.tail.foldLeft(lines.headOption.fold(0)(count))(_ + count(_))
if width > 0 && len > 0 then (len - 1 + width) / width else 0
if lines.nonEmpty then lines.tail.foldLeft(lines.headOption.fold(0)(count))(_ + count(_))
else 0
}
private[sbt] def flush(): Unit = printStream.flush()
private[sbt] def readArrow: Int = withRawInput {
@@ -199,28 +197,25 @@ trait Terminal extends AutoCloseable {
val ESC = '\u001B'
val EOT = '\u0004'
def readBracket: Int =
in.read() match {
in.read() match
case '[' => readAnsiControl
case _ => 0
}
def readAnsiControl: Int =
in.read() match {
in.read() match
case 'A' => Terminal.VK_UP
case 'B' => Terminal.VK_DOWN
case 'C' => Terminal.VK_RIGHT
case 'D' => Terminal.VK_LEFT
case _ => 0
}
in.read() match {
in.read() match
case ESC => readBracket
// Ctrl+D to quit
case EOT => -1
case c => c
}
}
}
end Terminal
object Terminal {
object Terminal:
private[sbt] final val VK_UP = 256
private[sbt] final val VK_DOWN = 257
private[sbt] final val VK_RIGHT = 258
@@ -228,26 +223,25 @@ object Terminal {
val NO_BOOT_CLIENTS_CONNECTED: Int = -2
// Disable noisy jline log spam
if (System.getProperty("sbt.jline.verbose", "false") != "true")
if System.getProperty("sbt.jline.verbose", "false") != "true" then
jline.internal.Log.setOutput(new PrintStream(_ => {}, false))
private val isCI = System.getProperty("sbt.ci", "") == "true" ||
sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
def consoleLog(string: String): Unit = {
def consoleLog(string: String): Unit =
try Terminal.console.printStream.println(s"[info] $string")
catch { case _: IOException => }
}
catch
case _: IOException =>
private val writeLock = new AnyRef
def withWriteLock[A](f: => A): A = {
def withWriteLock[A](f: => A): A =
writeLock.synchronized {
f
}
}
private[sbt] def set(terminal: Terminal): Terminal = activeTerminal.getAndSet(terminal)
extension (term: Terminal) {
extension (term: Terminal)
def ansi(richString: => String, string: => String): String =
if (term.isAnsiSupported) richString else string
if term.isAnsiSupported then richString else string
/*
* Whenever we are dealing with JLine, which is true in sbt's ConsoleReader
* as well as in the scala `console` task, we need to provide a jline.Terminal2
@@ -255,9 +249,9 @@ object Terminal {
* already wraps a jline terminal, so we can just return the wrapped jline
* terminal.
*/
private[sbt] def toJLine: jline.Terminal & jline.Terminal2 = term match {
private[sbt] def toJLine: jline.Terminal & jline.Terminal2 = term match
case _ =>
new jline.Terminal with jline.Terminal2 {
new jline.Terminal with jline.Terminal2:
override def init(): Unit = {}
override def restore(): Unit = {}
override def reset(): Unit = {}
@@ -279,21 +273,17 @@ object Terminal {
term.getNumericCapability(capability)
override def getStringCapability(capability: String): String =
term.getStringCapability(capability)
}
}
}
end extension
/*
* Closes the standard input and output streams for the process. This allows
* the sbt client to detach from the server it launches.
*/
def close(): Unit = {
if (!hasConsole) {
def close(): Unit =
if !hasConsole then
originalOut.close()
originalIn.close()
originalErr.close()
}
}
/**
* Returns true if System.in is attached. When sbt is run as a subprocess, like in scripted or as
@@ -305,24 +295,21 @@ object Terminal {
*/
def systemInIsAttached: Boolean = attached.get
def read: Int = inputStream.get match {
def read: Int = inputStream.get match
case null => -1
case is => is.read
}
/**
* Returns an InputStream that will throw a [[ClosedChannelException]] if read returns -1.
* @return
* the wrapped InputStream.
*/
private[sbt] def throwOnClosedSystemIn(in: InputStream): InputStream = new InputStream {
private[sbt] def throwOnClosedSystemIn(in: InputStream): InputStream = new InputStream:
override def available(): Int = in.available()
override def read(): Int = in.read() match {
override def read(): Int = in.read() match
case -1 => throw new ClosedChannelException
case r if r >= 0 => r
case _ => -1
}
}
/**
* Provides a wrapper around System.in. The wrapped stream in will check if the terminal is
@@ -342,14 +329,13 @@ object Terminal {
private val hasProgress: AtomicBoolean = new AtomicBoolean(false)
private[sbt] def parseLogOption(s: String): Option[Boolean] =
s.toLowerCase match {
s.toLowerCase match
case "always" => Some(true)
case "auto" => None
case "never" => Some(false)
case "true" => Some(true)
case "false" => Some(false)
case _ => None
}
/**
* Indicates whether formatting has been disabled in environment variables.
@@ -357,27 +343,23 @@ object Terminal {
* 3. -Dsbt.colour=always/auto/never/true/false 4.
* -Dsbt.log.format=always/auto/never/true/false
*/
private lazy val logFormatEnabled: Option[Boolean] = {
sys.props.get("sbt.log.noformat") match {
private lazy val logFormatEnabled: Option[Boolean] =
sys.props.get("sbt.log.noformat") match
case Some(_) => Some(!java.lang.Boolean.getBoolean("sbt.log.noformat"))
case _ => sys.props.get("sbt.log.format").flatMap(parseLogOption)
}
}
private[sbt] lazy val isAnsiSupported: Boolean = logFormatEnabled.getOrElse(useColorDefault)
private val isDumb = Some("dumb") == sys.env.get("TERM")
private def isDumbTerminal = isDumb || System.getProperty("jline.terminal", "") == "none"
private[sbt] val hasConsole = {
private[sbt] val hasConsole =
System.console != null && {
try {
try
val isTerminal = System.console.getClass.getMethod("isTerminal")
isTerminal.invoke(System.console).asInstanceOf[Boolean]
} catch {
catch
case _: NoSuchMethodException =>
true
}
}
}
/**
* Pure function that determines whether color output should be enabled.
@@ -418,7 +400,7 @@ object Terminal {
private[sbt] lazy val isColorEnabled = useColorDefault
private[sbt] def red(str: String, doRed: Boolean): String =
if (isColorEnabled && doRed) Console.RED + str + Console.RESET
if isColorEnabled && doRed then Console.RED + str + Console.RESET
else str
private def hasVirtualIO = System.getProperty("sbt.io.virtual", "") == "true" || !isCI
@@ -434,18 +416,18 @@ object Terminal {
* @return
* the result of the thunk
*/
private[sbt] def withStreams[T](isServer: Boolean, isSubProcess: Boolean)(f: => T): T = {
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
if ((hasConsole && !isDumbTerminal) || isSubProcess)
if (hasConsole && !isDumbTerminal) || isSubProcess then
consoleTerminalHolder.set(newConsoleTerminal())
if (hasVirtualIO) {
if hasVirtualIO then
hasProgress.set(isServer && isAnsiSupported)
Terminal.set(consoleTerminalHolder.get)
try withOut(withIn(f))
finally {
finally
jline.TerminalFactory.reset()
if (isServer) {
console match {
if isServer then
console match
case c: ConsoleTerminal if !isWindows =>
/*
* Entering raw mode in this way causes the standard in InputStream
@@ -455,37 +437,34 @@ object Terminal {
* this on a background thread in case the read blocks indefinitely.
*/
c.system.enterRawMode()
val runnable: Runnable = () => {
val runnable: Runnable = () =>
try Util.ignoreResult(c.inputStream.read)
catch { case _: InterruptedException => }
}
catch
case _: InterruptedException =>
val thread = new Thread(runnable, "sbt-console-background-close")
thread.setDaemon(true)
thread.start()
// The thread should exit almost instantly but give it 200ms to spin up
thread.join(200)
if (thread.isAlive) thread.interrupt()
if thread.isAlive then thread.interrupt()
c.close()
case c => c.close()
}
} else {
console.close()
}
}
} else f
}
else console.close()
end if
end try
else f
end if
end withStreams
private object ProxyTerminal extends Terminal {
private def t: Terminal = {
private object ProxyTerminal extends Terminal:
private def t: Terminal =
val current = activeTerminal.get
// if the activeTerminal is yet to be initialized on use,
// initialize to the conventional simple terminal for compatibility and testing
if (current ne null) current
else {
if current ne null then current
else
Terminal.set(Terminal.SimpleTerminal)
activeTerminal.get
}
}
override private[sbt] def progressState: ProgressState = t.progressState
override private[sbt] def enterRawMode(): Unit = t.enterRawMode()
override private[sbt] def exitRawMode(): Unit = t.exitRawMode()
@@ -522,63 +501,55 @@ object Terminal {
override def getLines: Seq[String] = t.getLines
override private[sbt] def name: String = t.name
override def toString: String = s"ProxyTerminal(current = $t)"
}
end ProxyTerminal
private[sbt] def get: Terminal = ProxyTerminal
private[sbt] def current: Terminal = activeTerminal.get
private[sbt] def withIn[T](in: InputStream)(f: => T): T = {
private[sbt] def withIn[T](in: InputStream)(f: => T): T =
val original = inputStream.get
try {
try
inputStream.set(in)
System.setIn(in)
scala.Console.withIn(in)(f)
} finally {
finally
inputStream.set(original)
System.setIn(original)
}
}
private[sbt] def withOut[T](out: PrintStream)(f: => T): T = {
private[sbt] def withOut[T](out: PrintStream)(f: => T): T =
val originalOut = System.out
val originalErr = System.err
val originalProxyOut = ConsoleOut.getGlobalProxy
try {
try
ConsoleOut.setGlobalProxy(ConsoleOut.printStreamOut(out))
System.setOut(out)
System.setErr(out)
scala.Console.withErr(out)(scala.Console.withOut(out)(f))
} finally {
finally
ConsoleOut.setGlobalProxy(originalProxyOut)
System.setOut(originalOut)
System.setErr(originalErr)
}
}
val sepBytes = System.lineSeparator.getBytes("UTF-8")
private class LinePrintStream(outputStream: OutputStream)
extends PrintStream(outputStream, true) {
private class LinePrintStream(outputStream: OutputStream) extends PrintStream(outputStream, true):
override def println(s: String): Unit = withWriteLock {
out.write(s.getBytes("UTF-8") ++ sepBytes)
out.flush()
}
}
private val originalOut = new LinePrintStream(System.out)
private val originalErr = System.err
private val originalIn = System.in
private[sbt] class WriteableInputStream(in: InputStream, name: String)
extends SimpleInputStream
with AutoCloseable {
with AutoCloseable:
private val isRaw = new AtomicBoolean(false)
final def write(bytes: Int*): Unit = buffer.synchronized {
bytes.foreach(b => buffer.put(b))
}
def setRawMode(toggle: Boolean): Unit = {
def setRawMode(toggle: Boolean): Unit =
isRaw.set(toggle)
in match {
in match
case win: WindowsInputStream => win.setRawMode(toggle)
case _ =>
}
}
private val executor =
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
private val buffer = new LinkedBlockingQueue[Integer]
@@ -596,76 +567,67 @@ object Terminal {
* by setting the terminal to raw mode, which the input stream makes it non blocking,
* but this approach only works on posix platforms.
*/
private val runnable: Runnable = () => {
@tailrec def impl(): Unit = {
private val runnable: Runnable = () =>
@tailrec def impl(): Unit =
val _ = readQueue.take
val b = in.read
buffer.synchronized(buffer.put(b))
if (Thread.interrupted() || (b == -1 && isRaw.get)) closed.set(true)
if Thread.interrupted() || (b == -1 && isRaw.get) then closed.set(true)
else impl()
}
try impl()
catch { case _: InterruptedException => closed.set(true) }
}
catch case _: InterruptedException => closed.set(true)
executor.submit(runnable)
def read(result: LinkedBlockingQueue[Integer]): Unit =
if (!closed.get)
if !closed.get then
readThread.synchronized {
readThread.set(Thread.currentThread)
try
buffer.poll match {
buffer.poll match
case null =>
readQueue.put(())
result.put(buffer.take)
case b if b == -1 => throw new ClosedChannelException
case b => result.put(b)
}
finally readThread.set(null)
}
override def read(): Int = {
override def read(): Int =
val result = new LinkedBlockingQueue[Integer]
read(result)
result.poll match {
result.poll match
case null => -1
case i => i.toInt
}
}
def cancel(): Unit = readThread.synchronized {
Option(readThread.getAndSet(null)).foreach(_.interrupt())
readQueue.clear()
}
override def available(): Int = {
override def available(): Int =
buffer.size
}
override def close(): Unit = if (closed.compareAndSet(false, true)) {
override def close(): Unit = if closed.compareAndSet(false, true) then
executor.shutdownNow()
buffer.synchronized(buffer.put(-1: Integer))
()
}
}
private def nonBlockingIn(term: org.jline.terminal.Terminal): WriteableInputStream = {
val in = if (Util.isNonCygwinWindows) new WindowsInputStream(term, originalIn) else originalIn
end WriteableInputStream
private def nonBlockingIn(term: org.jline.terminal.Terminal): WriteableInputStream =
val in =
if Util.isNonCygwinWindows then new WindowsInputStream(term, originalIn) else originalIn
new WriteableInputStream(in, "console")
}
private val inputStream = new AtomicReference[InputStream](System.in)
private def withOut[T](f: => T): T = {
try {
private def withOut[T](f: => T): T =
try
System.setOut(proxyPrintStream)
System.setErr(proxyErrorStream)
scala.Console.withErr(proxyErrorStream)(scala.Console.withOut(proxyPrintStream)(f))
} finally {
finally
System.setOut(originalOut)
System.setErr(originalErr)
}
}
private def withIn[T](f: => T): T =
try {
try
inputStream.set(proxyInputStream)
System.setIn(proxyInputStream)
scala.Console.withIn(proxyInputStream)(f)
} finally System.setIn(originalIn)
finally System.setIn(originalIn)
private[sbt] def withPrintStream[T](f: PrintStream => T): T = console.withPrintStream(f)
private val attached = new AtomicBoolean(true)
@@ -708,23 +670,19 @@ object Terminal {
private[sbt] def setBootStreams(
bootInputStream: InputStream,
bootOutputStream: OutputStream
): Unit = {
): Unit =
bootInputStreamHolder.set(bootInputStream)
bootOutputStreamHolder.set(bootOutputStream)
}
private[sbt] trait SimpleInputStream extends InputStream {
private[sbt] trait SimpleInputStream extends InputStream:
override def read(b: Array[Byte]): Int = read(b, 0, b.length)
override def read(b: Array[Byte], off: Int, len: Int): Int = {
read() match {
override def read(b: Array[Byte], off: Int, len: Int): Int =
read() match
case -1 => -1
case byte =>
b(off) = byte.toByte
1
}
}
}
private object proxyInputStream extends SimpleInputStream {
private object proxyInputStream extends SimpleInputStream:
private val isScripted = System.getProperty("sbt.scripted", "false") == "true"
/*
* This is to handle the case when a remote client starts sbt and the build fails.
@@ -736,98 +694,84 @@ object Terminal {
* of the input streams and periodically interrupt the thread to switch between
* the two input streams.
*/
private class ReadThread extends Thread with AutoCloseable {
private class ReadThread extends Thread with AutoCloseable:
val result = new LinkedBlockingQueue[Integer]
val running = new AtomicBoolean(true)
setDaemon(true)
start()
override def run(): Unit = while (running.get) {
bootInputStreamHolder.get match {
override def run(): Unit = while running.get do
bootInputStreamHolder.get match
case null =>
case is =>
def readFrom(inputStream: InputStream) =
try {
if (running.get) {
inputStream.read match {
try
if running.get then
inputStream.read match
case -1 =>
case `NO_BOOT_CLIENTS_CONNECTED` =>
if (!Terminal.hasConsole) {
if !Terminal.hasConsole then
result.put(-1)
running.set(false)
}
case i =>
result.put(i)
running.set(false)
}
}
} catch { case _: InterruptedException => }
catch
case _: InterruptedException =>
readFrom(is)
readFrom(activeTerminal.get().inputStream)
}
}
override def close(): Unit = if (running.compareAndSet(true, false)) this.interrupt()
}
override def read(): Int = {
if (isScripted) -1
else if (bootInputStreamHolder.get == null) activeTerminal.get().inputStream.read()
else {
override def close(): Unit = if running.compareAndSet(true, false) then this.interrupt()
end ReadThread
override def read(): Int =
if isScripted then -1
else if bootInputStreamHolder.get == null then activeTerminal.get().inputStream.read()
else
val thread = new ReadThread
@tailrec def poll(): Int = thread.result.poll(10, TimeUnit.MILLISECONDS) match {
@tailrec def poll(): Int = thread.result.poll(10, TimeUnit.MILLISECONDS) match
case null =>
thread.interrupt()
poll()
case i => i
}
poll()
}
}
override def available(): Int =
if (isScripted) 0
if isScripted then 0
else
bootInputStreamHolder.get match {
bootInputStreamHolder.get match
case null => activeTerminal.get().inputStream.available()
case stream => stream.available() + activeTerminal.get().inputStream.available()
}
}
private object proxyOutputStream extends OutputStream {
end proxyInputStream
private object proxyOutputStream extends OutputStream:
private def os: OutputStream = activeTerminal.get().outputStream
def write(byte: Int): Unit = {
def write(byte: Int): Unit =
os.write(byte)
os.flush()
if (byte == 10) os.flush()
}
if byte == 10 then os.flush()
override def write(bytes: Array[Byte]): Unit = write(bytes, 0, bytes.length)
override def write(bytes: Array[Byte], offset: Int, len: Int): Unit = {
override def write(bytes: Array[Byte], offset: Int, len: Int): Unit =
os.write(bytes, offset, len)
os.flush()
}
override def flush(): Unit = os.flush()
}
private val proxyPrintStream = new LinePrintStream(proxyOutputStream) {
private val proxyPrintStream = new LinePrintStream(proxyOutputStream):
override def toString: String = s"proxyPrintStream($proxyOutputStream)"
}
private object proxyErrorOutputStream extends OutputStream {
private object proxyErrorOutputStream extends OutputStream:
private def os: OutputStream = activeTerminal.get().errorStream
def write(byte: Int): Unit = os.write(byte)
override def write(bytes: Array[Byte]): Unit = write(bytes, 0, bytes.length)
override def write(bytes: Array[Byte], offset: Int, len: Int): Unit =
os.write(bytes, offset, len)
override def flush(): Unit = os.flush()
}
private object proxyErrorStream extends PrintStream(proxyErrorOutputStream, true)
private lazy val isWindows =
System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
private object WrappedSystemIn extends SimpleInputStream {
private object WrappedSystemIn extends SimpleInputStream:
private val in = proxyInputStream
override def available(): Int = if (attached.get) in.available() else 0
override def available(): Int = if attached.get then in.available() else 0
override def read(): Int = synchronized {
if (attached.get) {
if attached.get then
val res = in.read()
if (res == -1) attached.set(false)
if res == -1 then attached.set(false)
res
} else -1
else -1
}
}
/*
* When the server is booted by a remote client, it may not be able to accurately
@@ -844,7 +788,7 @@ object Terminal {
)
private[sbt] val TERMINAL_PROPS = "SBT_TERMINAL_PROPS"
private val props = sys.env.get(TERMINAL_PROPS) flatMap { p =>
p.split(",") match {
p.split(",") match
case Array(width, height, ansi, color, supershell) =>
Try(
new Props(
@@ -856,29 +800,26 @@ object Terminal {
)
).toOption
case _ => None
}
}
private[sbt] def startedByRemoteClient = props.isDefined
private def newConsoleTerminal(): Terminal = {
private def newConsoleTerminal(): Terminal =
val system = JLine3.system
new ConsoleTerminal(nonBlockingIn(system), originalOut, system)
}
private[sbt] def reset(): Unit = {
private[sbt] def reset(): Unit =
jline.TerminalFactory.reset()
console.close()
if (hasConsole && !isDumbTerminal) consoleTerminalHolder.set(newConsoleTerminal())
}
if hasConsole && !isDumbTerminal then consoleTerminalHolder.set(newConsoleTerminal())
// translate explicit class names to type in order to support
// older Scala, since it shaded classes but not the system property
private def fixTerminalProperty(): Unit = {
private def fixTerminalProperty(): Unit =
val terminalProperty = "jline.terminal"
val newValue =
if (!isAnsiSupported && System.getProperty("sbt.io.virtual", "") == "false") "none"
if !isAnsiSupported && System.getProperty("sbt.io.virtual", "") == "false" then "none"
else
System.getProperty(terminalProperty) match {
System.getProperty(terminalProperty) match
case "jline.UnixTerminal" => "unix"
case null if System.getProperty("sbt.cygwin") != null => "unix"
case "jline.WindowsTerminal" => "windows"
@@ -886,26 +827,20 @@ object Terminal {
case "jline.UnsupportedTerminal" => "none"
case null if isDumb => "none"
case x => x
}
if (newValue != null) {
if newValue != null then
System.setProperty(terminalProperty, newValue)
()
}
}
fixTerminalProperty()
private[sbt] def createReader(term: Terminal, prompt: Prompt): ConsoleReader = {
new ConsoleReader(term.inputStream, term.outputStream, term.toJLine) {
private[sbt] def createReader(term: Terminal, prompt: Prompt): ConsoleReader =
new ConsoleReader(term.inputStream, term.outputStream, term.toJLine):
override def readLine(prompt: String, mask: Character): String =
term.withRawInput(super.readLine(prompt, mask))
override def readLine(prompt: String): String = term.withRawInput(super.readLine(prompt))
}
}
def console: Terminal = consoleTerminalHolder.get match {
def console: Terminal = consoleTerminalHolder.get match
case null => throw new IllegalStateException("Uninitialized terminal.")
case term => term
}
private val capabilityMap =
org.jline.utils.InfoCmp.Capability.values().map(c => c.toString -> c).toMap
@@ -917,67 +852,62 @@ object Terminal {
in: WriteableInputStream,
out: OutputStream,
private[util] val system: org.jline.terminal.Terminal,
) extends TerminalImpl(in, out, originalErr, "console0") {
) extends TerminalImpl(in, out, originalErr, "console0"):
private val rawMode = new AtomicBoolean(false)
if (Util.isWindows && hasConsole) {
if Util.isWindows && hasConsole then
// It is necessary to enter and exit raw mode in order to get the windows
// console to echo input.
enterRawMode()
exitRawMode()
}
override private[sbt] def getSizeImpl: (Int, Int) = {
override private[sbt] def getSizeImpl: (Int, Int) =
val size = system.getSize
(size.getColumns, size.getRows)
}
override lazy val isAnsiSupported: Boolean =
!isDumbTerminal && Terminal.isAnsiSupported && !isCI
override private[sbt] def progressState: ProgressState = consoleProgressState.get
override def isSuccessEnabled: Boolean = true
private lazy val echoEnabled: AtomicBoolean = new AtomicBoolean({
try system.echo()
catch {
case _: InterruptedIOException => false
}
catch case _: InterruptedIOException => false
})
override def isEchoEnabled: Boolean = echoEnabled.get()
override def setEchoEnabled(toggle: Boolean): Unit =
try {
try
Util.ignoreResult(system.echo(toggle))
echoEnabled.set(toggle)
} catch { case _: InterruptedIOException => }
catch
case _: InterruptedIOException =>
override def getBooleanCapability(capability: String): Boolean =
capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
override def getNumericCapability(capability: String): Integer =
capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
override def getStringCapability(capability: String): String = {
override def getStringCapability(capability: String): String =
val res = capabilityMap.get(capability).fold(null: String)(system.getStringCapability)
res
}
override private[sbt] def restore(): Unit = exitRawMode()
override private[sbt] def getAttributes: Map[String, String] =
Try(JLine3.toMap(system.getAttributes)).getOrElse(Map.empty)
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = {
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
system.setAttributes(JLine3.attributesFromMap(attributes))
JLine3.setEnableProcessInput()
}
override private[sbt] def setSize(width: Int, height: Int): Unit =
system.setSize(new org.jline.terminal.Size(width, height))
override def inputStream: InputStream = in
override private[sbt] def enterRawMode(): Unit =
if (rawMode.compareAndSet(false, true) && hasConsole) {
if rawMode.compareAndSet(false, true) && hasConsole then
in.setRawMode(true)
try JLine3.enterRawMode(system)
catch { case _: java.io.IOError => }
}
catch
case _: java.io.IOError =>
override private[sbt] def exitRawMode(): Unit =
if (rawMode.compareAndSet(true, false) && hasConsole) {
if rawMode.compareAndSet(true, false) && hasConsole then
in.setRawMode(false)
try JLine3.exitRawMode(system)
catch { case _: java.io.IOError => }
}
catch
case _: java.io.IOError =>
override lazy val isColorEnabled: Boolean =
props
.map(_.color)
@@ -986,65 +916,59 @@ object Terminal {
override lazy val isSupershellEnabled: Boolean =
props
.map(_.supershell)
.getOrElse(System.getProperty("sbt.supershell") match {
.getOrElse(System.getProperty("sbt.supershell") match
case null =>
!(sys.env.contains("BUILD_NUMBER") || sys.env
.contains("CI")) && isColorEnabled && !Util.isEmacs
case "true" => true
case _ => false
})
override def close(): Unit = {
try {
case _ => false)
override def close(): Unit =
try
system.setAttributes(JLine3.initialAttributes.get)
system.close()
in.close()
} catch { case NonFatal(_) => }
catch
case NonFatal(_) =>
super.close()
}
}
end ConsoleTerminal
private[sbt] abstract class TerminalImpl private[sbt] (
val in: WriteableInputStream,
val out: OutputStream,
override val errorStream: OutputStream,
override private[sbt] val name: String
) extends Terminal { self =>
) extends Terminal:
self =>
private[sbt] def getSizeImpl: (Int, Int)
private val sizeRefreshPeriod = 1.second
private val size =
new AtomicReference[((Int, Int), Deadline)](((1, 1), Deadline.now - 1.day))
private def setSize() = size.set((Try(getSizeImpl).getOrElse((1, 1)), Deadline.now))
private def getSize = size.get match {
private def getSize = size.get match
case (s, d) if (d + sizeRefreshPeriod).isOverdue() =>
setSize()
size.get._1
case (s, _) => s
}
override def getWidth: Int = getSize._1
override def getHeight: Int = getSize._2
private val rawMode = new AtomicBoolean(false)
def throwIfClosed[R](f: => R): R = if (isStopped.get) throw new ClosedChannelException else f
def throwIfClosed[R](f: => R): R = if isStopped.get then throw new ClosedChannelException else f
override def getLastLine: Option[String] = progressState.currentLine
override def getLines: Seq[String] = progressState.getLines
private val combinedOutputStream = new OutputStream {
override def write(b: Int): Unit = {
private val combinedOutputStream = new OutputStream:
override def write(b: Int): Unit =
Option(bootOutputStreamHolder.get).foreach(_.write(b))
out.write(b)
}
override def write(b: Array[Byte]): Unit = {
override def write(b: Array[Byte]): Unit =
write(b, 0, b.length)
}
override def write(b: Array[Byte], offset: Int, len: Int): Unit = {
override def write(b: Array[Byte], offset: Int, len: Int): Unit =
Option(bootOutputStreamHolder.get).foreach(_.write(b, offset, len))
out.write(b, offset, len)
}
override def flush(): Unit = {
override def flush(): Unit =
Option(bootOutputStreamHolder.get).foreach(_.flush())
out.flush()
}
}
override val outputStream = new OutputStream {
override val outputStream = new OutputStream:
override def write(b: Int): Unit = throwIfClosed {
write(Array((b & 0xff).toByte))
}
@@ -1055,19 +979,18 @@ object Terminal {
write(Arrays.copyOfRange(b, offset, offset + length))
}
override def flush(): Unit = combinedOutputStream.flush()
}
private def doWrite(rawBytes: Array[Byte]): Unit = withPrintStream { ps =>
val (toWrite, len) =
if (rawBytes.contains(27.toByte)) {
if (!self.isAnsiSupported || !self.isColorEnabled)
if rawBytes.contains(27.toByte) then
if !self.isAnsiSupported || !self.isColorEnabled then
EscHelpers.strip(
rawBytes,
stripAnsi = !self.isAnsiSupported,
stripColor = !self.isColorEnabled
)
else (rawBytes, rawBytes.length)
} else (rawBytes, rawBytes.length)
val bytes = if (len < toWrite.length) toWrite.take(len) else toWrite
else (rawBytes, rawBytes.length)
val bytes = if len < toWrite.length then toWrite.take(len) else toWrite
progressState.write(TerminalImpl.this, bytes, ps, hasProgress.get && !rawMode.get)
}
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)
@@ -1076,37 +999,33 @@ object Terminal {
private[sbt] def write(bytes: Int*): Unit = in.write(bytes*)
private val isStopped = new AtomicBoolean(false)
override def getLineHeightAndWidth(line: String): (Int, Int) = getWidth match {
override def getLineHeightAndWidth(line: String): (Int, Int) = getWidth match
case width if width > 0 =>
val position = EscHelpers.cursorPosition(line)
val count = (position + width - 1) / width
(count, position - (math.max((count - 1), 0) * width))
case _ => (0, 0)
}
private[sbt] def withRawOutput[R](f: => R): R = {
private[sbt] def withRawOutput[R](f: => R): R =
rawMode.set(true)
try f
finally rawMode.set(false)
}
private val rawPrintStream: PrintStream = new LinePrintStream(combinedOutputStream)
override def withPrintStream[T](f: PrintStream => T): T =
writeLock.synchronized(f(rawPrintStream))
override def close(): Unit = if (isStopped.compareAndSet(false, true)) {
in.close()
}
}
private lazy val nullInputStream: InputStream = () => {
override def close(): Unit = if isStopped.compareAndSet(false, true) then in.close()
end TerminalImpl
private lazy val nullInputStream: InputStream = () =>
try this.synchronized(this.wait())
catch { case _: InterruptedException => }
catch
case _: InterruptedException =>
-1
}
private[sbt] class DefaultTerminal(
val inputStream: InputStream,
val outputStream: OutputStream,
val errorStream: OutputStream
) extends Terminal {
) extends Terminal:
override def close(): Unit = {}
override private[sbt] def progressState: ProgressState = new ProgressState(1)
override private[sbt] def enterRawMode(): Unit = {}
@@ -1134,5 +1053,5 @@ object Terminal {
override private[sbt] def withPrintStream[T](f: PrintStream => T): T = f(printStream)
override private[sbt] def write(bytes: Int*): Unit = {}
override private[sbt] def withRawOutput[R](f: => R): R = f
}
}
end DefaultTerminal
end Terminal
@@ -16,22 +16,18 @@ import org.jline.utils.InfoCmp.Capability
import scala.annotation.tailrec
import Terminal.SimpleInputStream
private object WindowsSupport {
def getConsoleMode = {
private object WindowsSupport:
def getConsoleMode =
val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
val mode = new Array[Int](1);
if (Kernel32.GetConsoleMode(console, mode) == 0) -1 else mode.head
}
def setConsoleMode(mode: Int): Unit = {
if Kernel32.GetConsoleMode(console, mode) == 0 then -1 else mode.head
def setConsoleMode(mode: Int): Unit =
val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
Kernel32.SetConsoleMode(console, mode)
()
}
def readConsoleInput(count: Int) = {
def readConsoleInput(count: Int) =
val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
Kernel32.readConsoleInputHelper(console, 1, false)
}
}
/*
* We need a special input stream for windows because special key events
* like arrow keys are not reported by System.in. What makes this extra
@@ -45,23 +41,22 @@ private object WindowsSupport {
* raw mode.
*/
private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: InputStream)
extends SimpleInputStream {
extends SimpleInputStream:
private val RIGHT_ALT_PRESSED = 0x0001;
private val LEFT_ALT_PRESSED = 0x0002;
private val RIGHT_CTRL_PRESSED = 0x0004;
private val LEFT_CTRL_PRESSED = 0x0008;
private val SHIFT_PRESSED = 0x0010;
private def getCapability(cap: Capability): String = term.getStringCapability(cap) match {
private def getCapability(cap: Capability): String = term.getStringCapability(cap) match
case null => null
case c => c.replace("\\E", "\u001B")
}
/*
* This function is a hybrid of jline 2 WindowsTerminal.readConsoleInput
* and jline3 AbstractTerminal.getEscapeSequence.
*/
private def readConsoleInput(): Array[Byte] = {
WindowsSupport.readConsoleInput(1) match {
private def readConsoleInput(): Array[Byte] =
WindowsSupport.readConsoleInput(1) match
case null => Array.empty
case events =>
val sb = new StringBuilder();
@@ -71,26 +66,20 @@ private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: In
val isCtrl = (controlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) > 0;
val isAlt = (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) > 0;
val isShift = (controlKeyState & SHIFT_PRESSED) > 0;
if (keyEvent.keyDown) {
if (keyEvent.uchar > 0) {
if (
((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
if keyEvent.keyDown then
if keyEvent.uchar > 0 then
if ((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
&& isAlt && !isCtrl
) {
sb.append('\u001B') // ESC
}
if (isShift && keyEvent.keyCode == 9) {
getCapability(Capability.key_btab) match {
then sb.append('\u001B') // ESC
if isShift && keyEvent.keyCode == 9 then
getCapability(Capability.key_btab) match
case null => sb.append(keyEvent.uchar)
case cap => sb.append(cap)
}
} else {
sb.append(keyEvent.uchar)
}
} else {
else sb.append(keyEvent.uchar)
else
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// just add support for basic editing keys (no control state, no numpad keys)
val escapeSequence = keyEvent.keyCode match {
val escapeSequence = keyEvent.keyCode match
case 0x21 /* VK_PRIOR PageUp*/ => getCapability(Capability.key_ppage);
case 0x22 /* VK_NEXT PageDown*/ => getCapability(Capability.key_npage);
case 0x24 /* VK_HOME */ => getCapability(Capability.key_home)
@@ -119,36 +108,27 @@ private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: In
case 0x2e /* VK_DELETE */ =>
Option(getCapability(Capability.key_dc)).getOrElse("\u001B[3~")
case _ => null
}
escapeSequence match {
escapeSequence match
case null =>
case es => (0 until keyEvent.repeatCount.toInt).foreach(_ => sb.append(es))
}
}
} else {
else
// key up event
// support ALT+NumPad input method
if (keyEvent.keyCode == 0x12 /*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
if keyEvent.keyCode == 0x12 /*VK_MENU ALT key*/ && keyEvent.uchar > 0 then
sb.append(keyEvent.uchar);
}
}
end if
}
sb.toString().getBytes()
}
}
private val raw: InputStream = new SimpleInputStream {
private val raw: InputStream = new SimpleInputStream:
val buffer = new LinkedBlockingQueue[Integer]
@tailrec
override def read(): Int = {
buffer.poll match {
override def read(): Int =
buffer.poll match
case null =>
readConsoleInput().foreach(b => buffer.put(b & 0xff))
if (!Thread.interrupted) read() else throw new InterruptedException
if !Thread.interrupted then read() else throw new InterruptedException
case b => b
}
}
}
private val isRaw = new AtomicBoolean(true)
private[sbt] def setRawMode(toggle: Boolean): Unit = isRaw.set(toggle)
override def read(): Int = if (isRaw.get) raw.read() else in.read()
}
override def read(): Int = if isRaw.get then raw.read() else in.read()
end WindowsInputStream
@@ -13,11 +13,11 @@ package util.codec
import sjsonnew.{ JsonWriter as JW, JsonReader as JR, JsonFormat as JF, * }
import sjsonnew.shaded.scalajson.ast.unsafe.*
trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
given JNullFormat: JF[JNull.type] = new JF[JNull.type] {
trait JValueFormats:
self: sjsonnew.BasicJsonProtocol =>
given JNullFormat: JF[JNull.type] = new JF[JNull.type]:
def write[J](x: JNull.type, b: Builder[J]) = b.writeNull()
def read[J](j: Option[J], u: Unbuilder[J]) = JNull
}
given JBooleanFormat: JF[JBoolean] = projectFormat(_.get, (x: Boolean) => JBoolean(x))
given JStringFormat: JF[JString] = projectFormat(_.value, (x: String) => JString(x))
@@ -28,34 +28,28 @@ trait JValueFormats { self: sjsonnew.BasicJsonProtocol =>
given JArrayFormat(using JF[JValue]): JF[JArray] =
projectFormat[JArray, Array[JValue]](_.value, JArray(_))
given JObjectJsonWriter: JW[JObject] = new JW[JObject] {
def write[J](x: JObject, b: Builder[J]) = {
given JObjectJsonWriter: JW[JObject] = new JW[JObject]:
def write[J](x: JObject, b: Builder[J]) =
b.beginObject()
x.value foreach (jsonField => JValueFormat.addField(jsonField.field, jsonField.value, b))
b.endObject()
}
}
given JValueJsonWriter: JW[JValue] = new JW[JValue] {
def write[J](x: JValue, b: Builder[J]) = x match {
given JValueJsonWriter: JW[JValue] = new JW[JValue]:
def write[J](x: JValue, b: Builder[J]) = x match
case x: JNull.type => JNullFormat.write(x, b)
case x: JBoolean => JBooleanFormat.write(x, b)
case x: JString => JStringFormat.write(x, b)
case x: JNumber => JNumberFormat.write(x, b)
case x: JArray => JArrayFormat.write(x, b)
case x: JObject => JObjectJsonWriter.write(x, b)
}
}
// This passes through JValue, or returns JNull instead of blowing up with unimplemented.
given JValueJsonReader: JR[JValue] = new JR[JValue] {
def read[J](j: Option[J], u: Unbuilder[J]) = j match {
given JValueJsonReader: JR[JValue] = new JR[JValue]:
def read[J](j: Option[J], u: Unbuilder[J]) = j match
case Some(x: JValue) => x
case Some(x) => sys.error(s"Unknown AST $x")
case _ => JNull
}
}
given JValueFormat: JF[JValue] =
jsonFormat[JValue](JValueJsonReader, JValueJsonWriter)
}
end JValueFormats
@@ -12,10 +12,11 @@ import sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Position
import java.util.Optional
trait PositionFormats { self: sjsonnew.BasicJsonProtocol =>
given PositionFormat: JsonFormat[Position] = new JsonFormat[Position] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Position = {
jsOpt match {
trait PositionFormats:
self: sjsonnew.BasicJsonProtocol =>
given PositionFormat: JsonFormat[Position] = new JsonFormat[Position]:
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Position =
jsOpt match
case Some(js) =>
unbuilder.beginObject(js)
val line0 = unbuilder.readField[Optional[java.lang.Integer]]("line")
@@ -33,7 +34,7 @@ trait PositionFormats { self: sjsonnew.BasicJsonProtocol =>
val endColumn0 = unbuilder.readField[Optional[java.lang.Integer]]("endColumn")
unbuilder.endObject()
new Position() {
new Position():
override val line = line0
override val lineContent = lineContent0
override val offset = offset0
@@ -48,12 +49,9 @@ trait PositionFormats { self: sjsonnew.BasicJsonProtocol =>
override val endLine = endLine0
override val endColumn = endColumn0
}
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: Position, builder: Builder[J]): Unit = {
override def write[J](obj: Position, builder: Builder[J]): Unit =
builder.beginObject()
builder.addField("line", obj.line)
builder.addField("lineContent", obj.lineContent)
@@ -70,6 +68,4 @@ trait PositionFormats { self: sjsonnew.BasicJsonProtocol =>
builder.addField("endColumn", obj.endColumn)
builder.endObject()
}
}
}
end PositionFormats
@@ -12,10 +12,11 @@ import xsbti.{ Problem, Severity, Position }
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import java.util.Optional
trait ProblemFormats { self: SeverityFormats & PositionFormats & sjsonnew.BasicJsonProtocol =>
given ProblemFormat: JsonFormat[Problem] = new JsonFormat[Problem] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Problem = {
jsOpt match {
trait ProblemFormats:
self: SeverityFormats & PositionFormats & sjsonnew.BasicJsonProtocol =>
given ProblemFormat: JsonFormat[Problem] = new JsonFormat[Problem]:
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Problem =
jsOpt match
case Some(js) =>
unbuilder.beginObject(js)
val category0 = unbuilder.readField[String]("category")
@@ -25,18 +26,15 @@ trait ProblemFormats { self: SeverityFormats & PositionFormats & sjsonnew.BasicJ
val rendered0 = unbuilder.readField[Optional[String]]("rendered")
unbuilder.endObject()
new Problem {
new Problem:
override val category = category0
override val position = position0
override val message = message0
override val severity = severity0
override val rendered = rendered0
}
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: Problem, builder: Builder[J]): Unit = {
override def write[J](obj: Problem, builder: Builder[J]): Unit =
builder.beginObject()
builder.addField("category", obj.category)
builder.addField("severity", obj.severity)
@@ -44,6 +42,4 @@ trait ProblemFormats { self: SeverityFormats & PositionFormats & sjsonnew.BasicJ
builder.addField("position", obj.position)
builder.addField("rendered", obj.rendered)
builder.endObject()
}
}
}
end ProblemFormats
@@ -11,27 +11,21 @@ package sbt.internal.util.codec
import _root_.sjsonnew.{ deserializationError, Builder, JsonFormat, Unbuilder }
import xsbti.Severity;
trait SeverityFormats { self: sjsonnew.BasicJsonProtocol =>
given SeverityFormat: JsonFormat[Severity] = new JsonFormat[Severity] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Severity = {
jsOpt match {
trait SeverityFormats:
self: sjsonnew.BasicJsonProtocol =>
given SeverityFormat: JsonFormat[Severity] = new JsonFormat[Severity]:
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Severity =
jsOpt match
case Some(js) =>
unbuilder.readString(js) match {
unbuilder.readString(js) match
case "Info" => Severity.Info
case "Warn" => Severity.Warn
case "Error" => Severity.Error
}
case None =>
deserializationError("Expected JsString but found None")
}
}
override def write[J](obj: Severity, builder: Builder[J]): Unit = {
val str = obj match {
override def write[J](obj: Severity, builder: Builder[J]): Unit =
val str = obj match
case Severity.Info => "Info"
case Severity.Warn => "Warn"
case Severity.Error => "Error"
}
builder.writeString(str)
}
}
}
@@ -12,11 +12,8 @@ package internal.util.codec
import sbt.util.ShowLines
import sbt.internal.util.SuccessEvent
trait SuccessEventShowLines {
trait SuccessEventShowLines:
given sbtSuccessEventShowLines: ShowLines[SuccessEvent] =
ShowLines[SuccessEvent]((e: SuccessEvent) => {
Vector(e.message)
})
}
ShowLines[SuccessEvent]((e: SuccessEvent) => Vector(e.message))
object SuccessEventShowLines extends SuccessEventShowLines
@@ -12,22 +12,20 @@ package internal.util.codec
import sbt.util.ShowLines
import sbt.internal.util.{ StackTrace, TraceEvent }
trait ThrowableShowLines {
trait ThrowableShowLines:
given sbtThrowableShowLines: ShowLines[Throwable] =
ShowLines[Throwable]((t: Throwable) => {
ShowLines[Throwable]((t: Throwable) =>
// 0 means enabled with default behavior. See StackTrace.scala.
val traceLevel = 0
List(StackTrace.trimmed(t, traceLevel))
})
}
)
object ThrowableShowLines extends ThrowableShowLines
trait TraceEventShowLines {
trait TraceEventShowLines:
given sbtTraceEventShowLines: ShowLines[TraceEvent] =
ShowLines[TraceEvent]((t: TraceEvent) => {
ShowLines[TraceEvent]((t: TraceEvent) =>
ThrowableShowLines.sbtThrowableShowLines.showLines(t.message)
})
}
)
object TraceEventShowLines extends TraceEventShowLines
@@ -8,7 +8,7 @@
package sbt.util
abstract class AbstractLogger extends Logger {
abstract class AbstractLogger extends Logger:
def getLevel: Level.Value
def setLevel(newLevel: Level.Value): Unit
def setTrace(flag: Int): Unit
@@ -23,8 +23,8 @@ abstract class AbstractLogger extends Logger {
def logAll(events: Seq[LogEvent]): Unit
/** Defined in terms of other methods in Logger and should not be called from them. */
final def log(event: LogEvent): Unit = {
event match {
final def log(event: LogEvent): Unit =
event match
case s: Success => success(s.msg)
case l: Log => log(l.level, l.msg)
case t: Trace => trace(t.exception)
@@ -32,6 +32,4 @@ abstract class AbstractLogger extends Logger {
case setT: SetTrace => setTrace(setT.level)
case setS: SetSuccess => setSuccessEnabled(setS.enabled)
case c: ControlEvent => control(c.event, c.msg)
}
}
}
end AbstractLogger
@@ -27,10 +27,9 @@ import xsbti.{
import scala.collection.mutable.ListBuffer
object InterfaceUtil {
def toSupplier[A](a: => A): Supplier[A] = new Supplier[A] {
object InterfaceUtil:
def toSupplier[A](a: => A): Supplier[A] = new Supplier[A]:
override def get: A = a
}
import java.util.function.Function as JavaFunction
def toJavaFunction[A1, R](f: A1 => R): JavaFunction[A1, R] =
@@ -39,35 +38,31 @@ object InterfaceUtil {
def t2[A1, A2](x: (A1, A2)): T2[A1, A2] = new ConcreteT2(x._1, x._2)
def toOption[A](m: Optional[A]): Option[A] =
if (m.isPresent) Some(m.get) else None
if m.isPresent then Some(m.get) else None
def toOptional[A](o: Option[A]): Optional[A] =
o match {
o match
case Some(v) => Optional.of(v)
case None => Optional.empty()
}
def jo2o[A](o: Optional[A]): Option[A] =
if (o.isPresent) Some(o.get)
if o.isPresent then Some(o.get)
else None
def o2jo[A](o: Option[A]): Optional[A] =
o match {
o match
case Some(v) => Optional.ofNullable(v)
case None => Optional.empty[A]()
}
def l2jl[A](l: List[A]): ju.List[A] = {
def l2jl[A](l: List[A]): ju.List[A] =
val jl = new ju.ArrayList[A](l.size)
l.foreach(jl.add(_))
jl
}
def jl2l[A](jl: ju.List[A]): List[A] = {
def jl2l[A](jl: ju.List[A]): List[A] =
val l = ListBuffer[A]()
jl.forEach(l += _)
l.toList
}
def position(
line0: Option[Integer],
@@ -144,21 +139,18 @@ object InterfaceUtil {
new ConcreteDiagnosticRelatedInformation(position, message)
private final class ConcreteT2[A1, A2](override val get1: A1, override val get2: A2)
extends T2[A1, A2] {
extends T2[A1, A2]:
override def toString: String = s"ConcreteT2($get1, $get2)"
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: ConcreteT2[?, ?] =>
this.get1 == o.get1 &&
this.get2 == o.get2
case _ => false
}
override def hashCode: Int = {
override def hashCode: Int =
var hash = 1
hash = hash * 31 + this.get1.##
hash = hash * 31 + this.get2.##
hash
}
}
private final class ConcretePosition(
line0: Option[Integer],
@@ -174,7 +166,7 @@ object InterfaceUtil {
startColumn0: Option[Integer],
endLine0: Option[Integer],
endColumn0: Option[Integer]
) extends Position {
) extends Position:
val line = o2jo(line0)
val lineContent = content
val offset = o2jo(offset0)
@@ -188,21 +180,17 @@ object InterfaceUtil {
override val startColumn = o2jo(startColumn0)
override val endLine = o2jo(endLine0)
override val endColumn = o2jo(endColumn0)
override def toString: String = {
val src = sourcePath0 match {
override def toString: String =
val src = sourcePath0 match
case Some(x) => s"$x"
case None => "none"
}
val line = line0 match {
val line = line0 match
case Some(x) => s":$x"
case None => ""
}
val offset = offset0 match {
val offset = offset0 match
case Some(x) => s":$x"
case None => ""
}
s"""$src$line$offset"""
}
private def toTuple(p: Position) =
(
p.line,
@@ -220,11 +208,10 @@ object InterfaceUtil {
p.endColumn,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: Position => toTuple(this) == toTuple(o)
case _ => false
}
}
end ConcretePosition
private final class ConcreteProblem(
override val category: String,
@@ -235,7 +222,7 @@ object InterfaceUtil {
diagnosticCode0: Option[DiagnosticCode],
diagnosticRelatedInformation0: List[DiagnosticRelatedInformation],
actions0: List[Action],
) extends Problem {
) extends Problem:
override val rendered = o2jo(rendered0)
override def diagnosticCode: Optional[DiagnosticCode] = o2jo(diagnosticCode0)
override def diagnosticRelatedInformation(): ju.List[DiagnosticRelatedInformation] =
@@ -258,17 +245,16 @@ object InterfaceUtil {
p.actions,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: Problem => toTuple(this) == toTuple(o)
case _ => false
}
}
end ConcreteProblem
private final class ConcreteAction(
override val title: String,
description0: Option[String],
override val edit: WorkspaceEdit,
) extends Action {
) extends Action:
override def description(): Optional[String] =
o2jo(description0)
override def toString(): String =
@@ -280,28 +266,24 @@ object InterfaceUtil {
a.edit,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: Action => toTuple(this) == toTuple(o)
case _ => false
}
}
private final class ConcreteWorkspaceEdit(changes0: List[TextEdit]) extends WorkspaceEdit {
private final class ConcreteWorkspaceEdit(changes0: List[TextEdit]) extends WorkspaceEdit:
override def changes(): ju.List[TextEdit] = l2jl(changes0)
override def toString(): String =
s"WorkspaceEdit($changes0)"
private def toTuple(w: WorkspaceEdit) = jl2l(w.changes)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: WorkspaceEdit => toTuple(this) == toTuple(o)
case _ => false
}
}
private final class ConcreteTextEdit(
override val position: Position,
override val newText: String
) extends TextEdit {
) extends TextEdit:
override def toString(): String =
s"TextEdit($position, $newText)"
private def toTuple(edit: TextEdit) =
@@ -310,16 +292,14 @@ object InterfaceUtil {
edit.newText,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: TextEdit => toTuple(this) == toTuple(o)
case _ => false
}
}
private final class ConcreteDiagnosticCode(
override val code: String,
explanation0: Option[String]
) extends DiagnosticCode {
) extends DiagnosticCode:
val explanation: Optional[String] = o2jo(explanation0)
override def toString(): String = s"DiagnosticCode($code)"
private def toTuple(c: DiagnosticCode) =
@@ -328,16 +308,14 @@ object InterfaceUtil {
c.explanation,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: DiagnosticCode => toTuple(this) == toTuple(o)
case _ => false
}
}
private final class ConcreteDiagnosticRelatedInformation(
override val position: Position,
override val message: String
) extends DiagnosticRelatedInformation {
) extends DiagnosticRelatedInformation:
override def toString(): String = s"DiagnosticRelatedInformation($position, $message)"
private def toTuple(info: DiagnosticRelatedInformation) =
(
@@ -345,9 +323,7 @@ object InterfaceUtil {
info.message,
)
override def hashCode: Int = toTuple(this).##
override def equals(o: Any): Boolean = o match {
override def equals(o: Any): Boolean = o match
case o: DiagnosticRelatedInformation => toTuple(this) == toTuple(o)
case _ => false
}
}
}
end InterfaceUtil
@@ -12,7 +12,7 @@ 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).
*/
object Level extends Enumeration {
object Level extends Enumeration:
val Debug = Value(1, "debug")
val Info = Value(2, "info")
val Warn = Value(3, "warn")
@@ -24,7 +24,7 @@ object Level extends Enumeration {
*/
val SuccessLabel = "success"
def union(a: Value, b: Value) = if (a.id < b.id) a else b
def union(a: Value, b: Value) = if a.id < b.id then a else b
def unionAll(vs: Seq[Value]) = vs reduceLeft union
/**
@@ -35,4 +35,4 @@ object Level extends Enumeration {
/** Same as apply, defined for use in pattern matching. */
private[sbt] def unapply(s: String) = apply(s)
}
end Level
@@ -17,10 +17,8 @@ final class SetTrace(val level: Int) extends LogEvent
final class SetSuccess(val enabled: Boolean) extends LogEvent
final class ControlEvent(val event: ControlEvent.Value, val msg: String) extends LogEvent
object ControlEvent {
enum Value {
object ControlEvent:
enum Value:
case Start
case Header
case Finish
}
}
@@ -12,26 +12,24 @@ import sbt.internal.util.{ Appender, ManagedLogger, TraceEvent, SuccessEvent }
import sbt.internal.util.appmacro.StringTypeTag
import scala.collection.concurrent
sealed abstract class LogExchange {
sealed abstract class LogExchange:
private[sbt] val stringCodecs: concurrent.Map[String, ShowLines[?]] = concurrent.TrieMap()
private[sbt] val builtInStringCodecs: Unit = initStringCodecs()
def logger(name: String): ManagedLogger = logger(name, None, None)
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger =
LoggerContext.globalContext.logger(name, channelName, execId)
def unbindLoggerAppenders(loggerName: String): Unit = {
def unbindLoggerAppenders(loggerName: String): Unit =
LoggerContext.globalContext.clearAppenders(loggerName)
}
def bindLoggerAppenders(
loggerName: String,
appenders: Seq[(Appender, Level.Value)]
): Unit = {
): Unit =
appenders.foreach(LoggerContext.globalContext.addAppender(loggerName, _))
()
}
private[sbt] def initStringCodecs(): Unit = {
private[sbt] def initStringCodecs(): Unit =
import sbt.internal.util.codec.SuccessEventShowLines.given
import sbt.internal.util.codec.ThrowableShowLines.given
import sbt.internal.util.codec.TraceEventShowLines.given
@@ -39,7 +37,6 @@ sealed abstract class LogExchange {
registerStringCodec[Throwable]
registerStringCodec[TraceEvent]
registerStringCodec[SuccessEvent]
}
def stringCodec[A](tag: String): Option[ShowLines[A]] =
stringCodecs.get(tag) map { _.asInstanceOf[ShowLines[A]] }
@@ -48,10 +45,9 @@ sealed abstract class LogExchange {
def getOrElseUpdateStringCodec[A](tag: String, v: ShowLines[A]): ShowLines[A] =
stringCodecs.getOrElseUpdate(tag, v).asInstanceOf[ShowLines[A]]
private[sbt] def registerStringCodec[A: ShowLines: StringTypeTag]: Unit = {
private[sbt] def registerStringCodec[A: ShowLines: StringTypeTag]: Unit =
val ev = implicitly[ShowLines[A]]
val tag = implicitly[StringTypeTag[A]]
val _ = getOrElseUpdateStringCodec(tag.key, ev)
}
}
end LogExchange
object LogExchange extends LogExchange
@@ -19,7 +19,7 @@ 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.
*/
abstract class Logger extends xLogger {
abstract class Logger extends xLogger:
final def verbose(message: => String): Unit = debug(message)
final def debug(message: => String): Unit = log(Level.Debug, message)
final def info(message: => String): Unit = log(Level.Info, message)
@@ -43,15 +43,14 @@ abstract class Logger extends xLogger {
def trace(msg: Supplier[Throwable]): Unit = trace(msg.get())
def success(msg: Supplier[String]): Unit = success(msg.get())
def log(level: Level.Value, msg: Supplier[String]): Unit = log(level, msg.get)
}
end Logger
object Logger {
def transferLevels(oldLog: AbstractLogger, newLog: AbstractLogger): Unit = {
object Logger:
def transferLevels(oldLog: AbstractLogger, newLog: AbstractLogger): Unit =
newLog.setLevel(oldLog.getLevel)
newLog.setTrace(oldLog.getTrace)
}
val Null: AbstractLogger = new AbstractLogger {
val Null: AbstractLogger = new AbstractLogger:
def getLevel: Level.Value = Level.Error
def setLevel(newLevel: Level.Value): Unit = ()
def getTrace: Int = 0
@@ -63,19 +62,17 @@ object Logger {
def trace(t: => Throwable): Unit = ()
def success(message: => String): Unit = ()
def log(level: Level.Value, message: => String): Unit = ()
}
implicit def absLog2PLog(log: AbstractLogger): ProcessLogger =
new BufferedLogger(log) with ProcessLogger
implicit def log2PLog(log: Logger): ProcessLogger = absLog2PLog(new FullLogger(log))
implicit def xlog2Log(lg: xLogger): Logger = lg match {
implicit def xlog2Log(lg: xLogger): Logger = lg match
case l: Logger => l
case _ => wrapXLogger(lg)
}
private def wrapXLogger(lg: xLogger): Logger = new Logger {
private def wrapXLogger(lg: xLogger): Logger = new Logger:
import InterfaceUtil.toSupplier
override def debug(msg: Supplier[String]): Unit = lg.debug(msg)
override def warn(msg: Supplier[String]): Unit = lg.warn(msg)
@@ -85,17 +82,14 @@ object Logger {
override def log(level: Level.Value, msg: Supplier[String]): Unit = lg.log(level, msg)
def trace(t: => Throwable): Unit = trace(toSupplier(t))
def success(s: => String): Unit = info(toSupplier(s))
def log(level: Level.Value, msg: => String): Unit = {
def log(level: Level.Value, msg: => String): Unit =
val fmsg = toSupplier(msg)
level match {
level match
case Level.Debug => lg.debug(fmsg)
case Level.Info => lg.info(fmsg)
case Level.Warn => lg.warn(fmsg)
case Level.Error => lg.error(fmsg)
}
}
}
def jo2o[A](o: Optional[A]): Option[A] = InterfaceUtil.jo2o(o)
def o2jo[A](o: Option[A]): Optional[A] = InterfaceUtil.o2jo(o)
}
end Logger
@@ -21,7 +21,7 @@ import java.util.concurrent.atomic.{ AtomicReference, AtomicBoolean }
* when it became clear that it was very difficult to manage the loggers and appenders without
* introducing memory leaks.
*/
sealed trait LoggerContext extends AutoCloseable {
sealed trait LoggerContext extends AutoCloseable:
def logger(name: String, channelName: Option[String], execId: Option[String]): ManagedLogger
def clearAppenders(loggerName: String): Unit
def addAppender(
@@ -30,83 +30,65 @@ sealed trait LoggerContext extends AutoCloseable {
): Unit
def appenders(loggerName: String): Seq[Appender]
def remove(name: String): Unit
}
object LoggerContext {
object LoggerContext:
private[sbt] lazy val globalContext: LoggerContext = new LoggerContext.LoggerContextImpl
private[util] class LoggerContextImpl extends LoggerContext {
private class Log extends MiniLogger {
private[util] class LoggerContextImpl extends LoggerContext:
private class Log extends MiniLogger:
private val consoleAppenders: AtomicReference[Vector[(Appender, Level.Value)]] =
new AtomicReference(Vector.empty)
def log(level: Level.Value, message: => String): Unit = {
def log(level: Level.Value, message: => String): Unit =
val toAppend = consoleAppenders.get.filter { (a, l) => level.compare(l) >= 0 }
if (toAppend.nonEmpty) {
if toAppend.nonEmpty then
val m = message
toAppend.foreach { (a, l) => a.appendLog(level, m) }
}
}
def log[T](level: Level.Value, message: ObjectEvent[T]): Unit = {
def log[T](level: Level.Value, message: ObjectEvent[T]): Unit =
consoleAppenders.get.foreach { (a, l) =>
if (level.compare(l) >= 0) a.appendObjectEvent(level, message)
if level.compare(l) >= 0 then a.appendObjectEvent(level, message)
}
}
def addAppender(newAppender: (Appender, Level.Value)): Unit =
Util.ignoreResult(consoleAppenders.updateAndGet(_ :+ newAppender))
def clearAppenders(): Unit = {
def clearAppenders(): Unit =
consoleAppenders.get.foreach { case (a, _) => a.close() }
consoleAppenders.set(Vector.empty)
}
def appenders: Seq[Appender] = consoleAppenders.get.map(_._1)
}
private val loggers = new ConcurrentHashMap[String, Log]
private val closed = new AtomicBoolean(false)
override def logger(
name: String,
channelName: Option[String],
execId: Option[String]
): ManagedLogger = {
if (closed.get) {
): ManagedLogger =
if closed.get then
throw new IllegalStateException("Tried to create logger for closed LoggerContext")
}
val xlogger = new Log
loggers.put(name, xlogger)
new ManagedLogger(name, channelName, execId, xlogger, Some(Terminal.get), this)
}
override def clearAppenders(loggerName: String): Unit = {
loggers.get(loggerName) match {
override def clearAppenders(loggerName: String): Unit =
loggers.get(loggerName) match
case null =>
case l => l.clearAppenders()
}
}
override def addAppender(
loggerName: String,
appender: (Appender, Level.Value)
): Unit = {
if (closed.get) {
): Unit =
if closed.get then
throw new IllegalStateException("Tried to add appender for closed LoggerContext")
}
loggers.get(loggerName) match {
loggers.get(loggerName) match
case null =>
case l => l.addAppender(appender)
}
}
override def appenders(loggerName: String): Seq[Appender] = {
loggers.get(loggerName) match {
override def appenders(loggerName: String): Seq[Appender] =
loggers.get(loggerName) match
case null => Nil
case l => l.appenders
}
}
override def remove(name: String): Unit = {
loggers.remove(name) match {
override def remove(name: String): Unit =
loggers.remove(name) match
case null =>
case l => l.clearAppenders()
}
}
def close(): Unit = {
def close(): Unit =
closed.set(true)
loggers.forEach((_, l) => l.clearAppenders())
loggers.clear()
}
}
end LoggerContextImpl
private[sbt] def apply() = new LoggerContextImpl
}
end LoggerContext
@@ -8,14 +8,10 @@
package sbt.util
trait ShowLines[A] {
trait ShowLines[A]:
def showLines(a: A): Seq[String]
}
object ShowLines {
object ShowLines:
def apply[A](f: A => Seq[String]): ShowLines[A] =
(a: A) => f(a)
extension [A: ShowLines](a: A) {
def lines: Seq[String] = implicitly[ShowLines[A]].showLines(a)
}
}
extension [A: ShowLines](a: A) def lines: Seq[String] = implicitly[ShowLines[A]].showLines(a)
@@ -16,7 +16,7 @@ import EscHelpers.{ ESC, hasEscapeSequence, isEscapeTerminator, removeEscapeSequ
import scala.annotation.tailrec
object Escapes extends Properties("Escapes") {
object Escapes extends Properties("Escapes"):
property("genTerminator only generates terminators") =
forAllNoShrink(genTerminator)((c: Char) => isEscapeTerminator(c))
@@ -69,39 +69,29 @@ object Escapes extends Properties("Escapes") {
(original == removed)
}
def diffIndex(expect: String, original: String): String = {
def diffIndex(expect: String, original: String): String =
@tailrec
def loop(i: Int): Option[String] = {
if (i < expect.length && i < original.length) {
if (expect.charAt(i) != original.charAt(i)) {
def loop(i: Int): Option[String] =
if i < expect.length && i < original.length then
if expect.charAt(i) != original.charAt(i) then
Some(
"Differing character, idx: " + i + ", char: " + original.charAt(i) +
", expected: " + expect.charAt(i)
)
} else {
loop(i + 1)
}
} else {
None
}
}
else loop(i + 1)
else None
loop(0).getOrElse(
if (expect.length != original.length) {
"Strings are different lengths!"
} else {
"No differences found"
}
if expect.length != original.length then "Strings are different lengths!"
else "No differences found"
)
}
final case class EscapeAndNot(escape: EscapeSequence, notEscape: String) {
final case class EscapeAndNot(escape: EscapeSequence, notEscape: String):
override def toString =
s"EscapeAntNot(escape = [$escape], notEscape = [${notEscape.map(_.toInt)}])"
}
// 2.10.5 warns on "implicit numeric widening" but it looks like a bug: https://issues.scala-lang.org/browse/SI-8450
final case class EscapeSequence(content: String, terminator: Char) {
if (!content.isEmpty) {
final case class EscapeSequence(content: String, terminator: Char):
if !content.isEmpty then
assert(
content.tail.forall(c => !isEscapeTerminator(c)),
"Escape sequence content contains an escape terminator: '" + content + "'"
@@ -110,14 +100,12 @@ object Escapes extends Properties("Escapes") {
(content.head == '[') || !isEscapeTerminator(content.head),
"Escape sequence content contains an escape terminator: '" + content.headOption + "'"
)
}
assert(isEscapeTerminator(terminator))
def makeString: String = s"$ESC$content$terminator"
override def toString =
if (content.isEmpty) s"ESC (${terminator.toInt})"
if content.isEmpty then s"ESC (${terminator.toInt})"
else s"ESC ($content) (${terminator.toInt})"
}
private def noEscape(s: String): String = s.replace(ESC, ' ')
@@ -125,13 +113,13 @@ object Escapes extends Properties("Escapes") {
oneOf(genKnownSequence, genTwoCharacterSequence, genArbitraryEscapeSequence)
lazy val genEscapePair: Gen[EscapeAndNot] =
for (esc <- genEscapeSequence; not <- genWithoutEscape) yield EscapeAndNot(esc, not)
for esc <- genEscapeSequence; not <- genWithoutEscape yield EscapeAndNot(esc, not)
lazy val genEscapePairs: Gen[List[EscapeAndNot]] = listOf(genEscapePair)
lazy val genArbitraryEscapeSequence: Gen[EscapeSequence] =
for (content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator)
yield new EscapeSequence("[" + content, term)
for content <- genWithoutTerminator if !content.isEmpty; term <- genTerminator
yield new EscapeSequence("[" + content, term)
lazy val genKnownSequence: Gen[EscapeSequence] =
oneOf((misc ++ setGraphicsMode ++ setMode ++ resetMode).map(toEscapeSequence))
@@ -141,8 +129,8 @@ object Escapes extends Properties("Escapes") {
lazy val misc = Seq("14;23H", "5;3f", "2A", "94B", "19C", "85D", "s", "u", "2J", "K")
lazy val setGraphicsMode: Seq[String] =
for (txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47)
yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m"
for txt <- 0 to 8; fg <- 30 to 37; bg <- 40 to 47
yield txt.toString + ";" + fg.toString + ";" + bg.toString + "m"
lazy val resetMode = setModeLike('I')
lazy val setMode = setModeLike('h')
@@ -159,8 +147,8 @@ object Escapes extends Properties("Escapes") {
lazy val genWithoutEscape: Gen[String] = genRawString.map(noEscape)
def genWithRandomEscapes: Gen[String] =
for (ls <- listOf(genRawString); end <- genRawString)
yield ls.mkString("", ESC.toString, ESC.toString + end)
for ls <- listOf(genRawString); end <- genRawString
yield ls.mkString("", ESC.toString, ESC.toString + end)
private def genRawString = Arbitrary.arbString.arbitrary
}
end Escapes
@@ -16,7 +16,7 @@ import Prop.*
import java.io.Writer
object LogWriterTest extends Properties("Log Writer") {
object LogWriterTest extends Properties("Log Writer"):
final val MaxLines = 100
final val MaxSegments = 10
@@ -37,27 +37,22 @@ object LogWriterTest extends Properties("Log Writer") {
* `Log` types and non-printable characters should be escaped
*/
def show(event: LogEvent): String =
event match {
event match
case l: Log => "Log('" + Escape(l.msg) + "', " + l.level + ")"
case _ => "Not Log"
}
/**
* 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) {
def logLines(writer: Writer, lines: List[List[ToLog]], newLine: String): Unit =
for line <- lines; section <- line do
val content = section.content
val normalized = Escape.newline(content, newLine)
if (section.byCharacter)
normalized.foreach(c => writer.write(c.toInt))
else
writer.write(normalized)
}
if section.byCharacter then normalized.foreach(c => writer.write(c.toInt))
else writer.write(normalized)
writer.flush()
}
/**
* Converts the given lines in segments to lines as Strings for checking the results of the test.
@@ -85,23 +80,22 @@ object LogWriterTest extends Properties("Log Writer") {
implicit lazy val arbLevel: Arbitrary[Level.Value] = Arbitrary(genLevel)
implicit def genLine(using logG: Gen[ToLog]): Gen[List[ToLog]] =
for (l <- listOf[ToLog](MaxSegments); last <- logG)
yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
for l <- listOf[ToLog](MaxSegments); last <- logG
yield (addNewline(last) :: l.filter(!_.content.isEmpty)).reverse
implicit def genLog(using content: Arbitrary[String], byChar: Arbitrary[Boolean]): Gen[ToLog] =
for (c <- content.arbitrary; by <- byChar.arbitrary) yield {
for c <- content.arbitrary; by <- byChar.arbitrary yield
assert(c != null)
new ToLog(removeNewlines(c), by)
}
given genNewLine: Gen[NewLine] =
for (str <- oneOf("\n", "\r", "\r\n")) yield new NewLine(str)
for str <- oneOf("\n", "\r", "\r\n") yield new NewLine(str)
given genLevel: Gen[Level.Value] =
oneOf(Level.values.toSeq)
given genOutput: Gen[Output] =
for (ls <- listOf[List[ToLog]](MaxLines); lv <- genLevel) yield new Output(ls, lv)
for ls <- listOf[List[ToLog]](MaxLines); lv <- genLevel yield new Output(ls, lv)
def removeNewlines(s: String) = s.replaceAll("""[\n\r]+""", "")
def addNewline(l: ToLog): ToLog =
@@ -112,63 +106,54 @@ object LogWriterTest extends Properties("Log Writer") {
def listOf[T](max: Int)(using content: Arbitrary[T]): Gen[List[T]] =
Gen.choose(0, max) flatMap (sz => listOfN(sz, content.arbitrary))
}
end LogWriterTest
/* Helper classes*/
final class Output(val lines: List[List[ToLog]], val level: Level.Value) {
final class Output(val lines: List[List[ToLog]], val level: Level.Value):
override def toString =
"Level: " + level + "\n" + lines.map(_.mkString).mkString("\n")
}
final class NewLine(val str: String) {
final class NewLine(val str: String):
override def toString = Escape(str)
}
final class ToLog(val content: String, val byCharacter: Boolean) {
final class ToLog(val content: String, val byCharacter: Boolean):
def contentOnly = Escape.newline(content, "")
override def toString =
if (content.isEmpty) "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
}
if content.isEmpty then "" else "ToLog('" + Escape(contentOnly) + "', " + byCharacter + ")"
/** Defines some utility methods for escaping unprintable characters. */
object Escape {
object Escape:
/** Escapes characters with code less than 20 by printing them as unicode escapes. */
def apply(s: String): String = {
def apply(s: String): String =
val builder = new StringBuilder(s.length)
for (c <- s) {
for c <- s do
val char = c.toInt
def escaped = pad(char.toHexString.toUpperCase, 4, '0')
if (c < 20) builder.append("\\u").append(escaped) else builder.append(c)
}
if c < 20 then builder.append("\\u").append(escaped) else builder.append(c)
builder.toString
}
def pad(s: String, minLength: Int, extra: Char) = {
def pad(s: String, minLength: Int, extra: Char) =
val diff = minLength - s.length
if (diff <= 0) s else List.fill(diff)(extra).mkString("", "", s)
}
if diff <= 0 then s else List.fill(diff)(extra).mkString("", "", s)
/** 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
}
if s.endsWith("\n") then s.substring(0, s.length - 1) + nl else s
/** Records logging events for later retrieval. */
final class RecordingLogger extends BasicLogger {
final class RecordingLogger extends BasicLogger:
private var events: List[LogEvent] = Nil
def getEvents = events.reverse
def ansiCodesSupported = true
def trace(t: => Throwable): Unit = { events ::= new Trace(t) }
def log(level: Level.Value, message: => String): Unit = { events ::= new Log(level, message) }
def success(message: => String): Unit = { events ::= new Success(message) }
def logAll(es: Seq[LogEvent]): Unit = { events :::= es.toList }
def trace(t: => Throwable): Unit = events ::= new Trace(t)
def log(level: Level.Value, message: => String): Unit = events ::= new Log(level, message)
def success(message: => String): Unit = events ::= new Success(message)
def logAll(es: Seq[LogEvent]): Unit = events :::= es.toList
def control(event: ControlEvent.Value, message: => String): Unit =
events ::= new ControlEvent(event, message)
}
@@ -15,7 +15,7 @@ import _root_.sbt.util.InterfaceUtil
import InterfaceUtil.{ jl2l, jo2o, l2jl }
import xsbti.*
object ProblemTest extends Properties {
object ProblemTest extends Properties:
override def tests: List[Test] = List(
property(
"All problems can toString",
@@ -42,7 +42,7 @@ object ProblemTest extends Properties {
def toStringCheck(p: Problem): Result =
Result.assert(p.toString() != "")
def equalityCheck(p: Problem): Result = {
def equalityCheck(p: Problem): Result =
val other = InterfaceUtil.problem(
p.category,
p.position,
@@ -56,25 +56,21 @@ object ProblemTest extends Properties {
Result
.assert(p == other)
.log(s"$p == $other")
}
def equalityCheck(c: DiagnosticCode): Result = {
def equalityCheck(c: DiagnosticCode): Result =
val other = copy(c)
Result.assert(c == other)
}
def equalityCheck(info: DiagnosticRelatedInformation): Result = {
def equalityCheck(info: DiagnosticRelatedInformation): Result =
val other = copy(info)
Result.assert(info == other)
}
def equalityCheck(a: Action): Result = {
def equalityCheck(a: Action): Result =
val other = copy(a)
Result.assert(a == other)
}
lazy val genProblem: Gen[Problem] =
for {
for
cat <- genString
pos <- genPosition
msg <- genString
@@ -83,7 +79,7 @@ object ProblemTest extends Properties {
code <- optDiagnosticCode
info <- listDiagnosticRelatedInformation
actions <- listAction
} yield InterfaceUtil.problem(
yield InterfaceUtil.problem(
cat,
pos,
msg,
@@ -98,19 +94,18 @@ object ProblemTest extends Properties {
Gen.choice1(genDiagnosticCode.map(Some(_)), Gen.constant(None))
lazy val genDiagnosticCode: Gen[DiagnosticCode] =
for {
code <- Gen.int(Range.linear(0, 1024))
} yield InterfaceUtil.diagnosticCode("E" + code.toString, None)
for code <- Gen.int(Range.linear(0, 1024))
yield InterfaceUtil.diagnosticCode("E" + code.toString, None)
lazy val genSeverity: Gen[Severity] =
Gen.element(Severity.Info, List(Severity.Warn, Severity.Error))
lazy val genPosition: Gen[Position] =
for {
for
line <- optIntGen
content <- genString
offset <- optIntGen
} yield InterfaceUtil.position(
yield InterfaceUtil.position(
line,
content,
offset,
@@ -130,39 +125,37 @@ object ProblemTest extends Properties {
Gen.list(genDiagnosticRelatedInformation, Range.linear(0, 2))
lazy val genDiagnosticRelatedInformation: Gen[DiagnosticRelatedInformation] =
for {
for
pos <- genPosition
message <- genString
} yield InterfaceUtil.diagnosticRelatedInformation(pos, message)
yield InterfaceUtil.diagnosticRelatedInformation(pos, message)
lazy val listAction: Gen[List[Action]] =
Gen.list(genAction, Range.linear(0, 2))
lazy val genAction: Gen[Action] =
for {
for
title <- genString
description <- optString
edit <- genWorkspaceEdit
} yield InterfaceUtil.action(title, description, edit)
yield InterfaceUtil.action(title, description, edit)
lazy val genWorkspaceEdit: Gen[WorkspaceEdit] =
for {
changes <- listTextEdit
} yield InterfaceUtil.workspaceEdit(changes)
for changes <- listTextEdit
yield InterfaceUtil.workspaceEdit(changes)
lazy val listTextEdit: Gen[List[TextEdit]] =
Gen.list(genTextEdit, Range.linear(0, 2))
lazy val genTextEdit: Gen[TextEdit] =
for {
for
pos <- genPosition
newText <- genString
} yield InterfaceUtil.textEdit(pos, newText)
yield InterfaceUtil.textEdit(pos, newText)
lazy val genUri: Gen[URI] =
for {
ssp <- genString
} yield new URI("file", "///" + ssp, null)
for ssp <- genString
yield new URI("file", "///" + ssp, null)
lazy val optString: Gen[Option[String]] =
Gen.choice1(genString.map(Some(_)), Gen.constant(None))
@@ -173,30 +166,26 @@ object ProblemTest extends Properties {
Gen.choice1(Gen.int(Range.linear(0, 1024)).map(Some(_)), Gen.constant(None))
private def copy(c: DiagnosticCode): DiagnosticCode =
new DiagnosticCode() {
new DiagnosticCode():
val code = c.code
override def explanation = c.explanation
}
private def copy(info: DiagnosticRelatedInformation): DiagnosticRelatedInformation =
new DiagnosticRelatedInformation() {
new DiagnosticRelatedInformation():
override def position = info.position
override def message = info.message
}
private def copy(a: Action): Action =
new Action {
new Action:
override def title = a.title
override def description = a.description
override def edit = copy(a.edit)
}
private def copy(edit: WorkspaceEdit): WorkspaceEdit =
() => l2jl(jl2l(edit.changes).map(copy))
private def copy(edit: TextEdit): TextEdit =
new TextEdit {
new TextEdit:
override val position = edit.position
override val newText = edit.newText
}
}
end ProblemTest
@@ -10,10 +10,8 @@ package sbt.internal.util
import sbt.util.*
object TestLogger {
def apply[T](f: Logger => T): T = {
object TestLogger:
def apply[T](f: Logger => T): T =
val log = new BufferedLogger(ConsoleLogger())
log.setLevel(Level.Debug)
log.bufferQuietly(f(log))
}
}
@@ -10,7 +10,7 @@ package sbt.internal.util
import verify.BasicTestSuite
object CleanStringSpec extends BasicTestSuite {
object CleanStringSpec extends BasicTestSuite:
test("EscHelpers should not modify normal strings") {
val cleanString = s"1234"
assert(EscHelpers.stripColorsAndMoves(cleanString) == cleanString)
@@ -108,4 +108,4 @@ object CleanStringSpec extends BasicTestSuite {
val expected2 = "[info] + com.acme.CoyoteTest.makeTrap 9ms \n"
assert(new String(bytes2, 0, len2) == expected2)
}
}
end CleanStringSpec
@@ -18,7 +18,12 @@ object TerminalColorSpec extends BasicTestSuite:
out: ByteArrayOutputStream
): Terminal.TerminalImpl =
new Terminal.TerminalImpl(
new Terminal.WriteableInputStream(new InputStream { def read() = -1 }, "test"),
new Terminal.WriteableInputStream(
new InputStream:
def read() = -1
,
"test"
),
out,
new ByteArrayOutputStream(),
"test"
@@ -36,17 +36,16 @@ as is this:
*/
/** Disjunction (or) of the list of clauses. */
final case class Clauses(clauses: List[Clause]) {
final case class Clauses(clauses: List[Clause]):
assert(clauses.nonEmpty, "At least one clause is required.")
override def toString: String =
s"Clauses(${clauses.mkString("\n")})"
}
/** When the `body` Formula succeeds, atoms in `head` are true. */
final case class Clause(body: Formula, head: Set[Atom])
/** A literal is an [[Atom]] or its negation ([[Negated]]). */
sealed abstract class Literal extends Formula {
sealed abstract class Literal extends Formula:
/** The underlying (positive) atom. */
def atom: Atom
@@ -54,56 +53,46 @@ sealed abstract class Literal extends Formula {
/** Negates this literal. */
def unary_! : Literal
}
/** A variable with name `label`. */
final case class Atom(label: String) extends Literal {
final case class Atom(label: String) extends Literal:
def atom = this
def unary_! : Negated = Negated(this)
}
/**
* A negated atom, in the sense of negation as failure, not logical negation. That is, it is true if
* `atom` is not known/defined.
*/
final case class Negated(atom: Atom) extends Literal {
final case class Negated(atom: Atom) extends Literal:
def unary_! : Atom = atom
}
/**
* A formula consists of variables, negation, and conjunction (and). (Disjunction is not currently
* included- it is modeled at the level of a sequence of clauses. This is less convenient when
* defining clauses, but is not less powerful.)
*/
sealed abstract class Formula {
sealed abstract class Formula:
/** Constructs a clause that proves `atoms` when this formula is true. */
def proves(atom: Atom, atoms: Atom*): Clause = Clause(this, (atom +: atoms).toSet)
/** Constructs a formula that is true iff this formula and `f` are both true. */
def &&(f: Formula): Formula = (this, f) match {
def &&(f: Formula): Formula = (this, f) match
case (True, x) => x
case (x, True) => x
case (And(as), And(bs)) => And(as ++ bs)
case (And(as), b: Literal) => And(as + b)
case (a: Literal, And(bs)) => And(bs + a)
case (a: Literal, b: Literal) => And(Set(a, b))
}
}
object Formula {
object Formula:
/** A conjunction of literals. */
final case class And(literals: Set[Literal]) extends Formula {
final case class And(literals: Set[Literal]) extends Formula:
assert(literals.nonEmpty, "'And' requires at least one literal.")
}
case object True extends Formula
}
object Logic {
object Logic:
def reduceAll(
clauses: List[Clause],
initialFacts: Set[Literal]
@@ -116,7 +105,7 @@ object Logic {
* `initialFacts` cannot be in the head of any clauses in `clause`. These restrictions ensure that
* the logic program has a unique minimal model.
*/
def reduce(clauses: Clauses, initialFacts: Set[Literal]): Either[LogicException, Matched] = {
def reduce(clauses: Clauses, initialFacts: Set[Literal]): Either[LogicException, Matched] =
val (posSeq, negSeq) = separate(initialFacts.toSeq)
val (pos, neg) = (posSeq.toSet, negSeq.toSet)
@@ -128,7 +117,6 @@ object Logic {
problem.toLeft(
reduce0(clauses, initialFacts, Matched.empty)
)
}
/**
* Verifies `initialFacts` are not in the head of any `clauses`. This avoids the situation where
@@ -138,19 +126,17 @@ object Logic {
private def checkOverlap(
clauses: Clauses,
initialFacts: Set[Atom]
): Option[InitialOverlap] = {
): Option[InitialOverlap] =
val as = atoms(clauses)
val initialOverlap = initialFacts.filter(as.inHead)
if (initialOverlap.nonEmpty) Some(new InitialOverlap(initialOverlap)) else None
}
if initialOverlap.nonEmpty then Some(new InitialOverlap(initialOverlap)) else None
private def checkContradictions(
pos: Set[Atom],
neg: Set[Atom]
): Option[InitialContradictions] = {
): Option[InitialContradictions] =
val contradictions = pos intersect neg
if (contradictions.nonEmpty) Some(new InitialContradictions(contradictions)) else None
}
if contradictions.nonEmpty then Some(new InitialContradictions(contradictions)) else None
sealed abstract class LogicException(override val toString: String)
@@ -170,21 +156,18 @@ object Logic {
)
/** Tracks proven atoms in the reverse order they were proved. */
final class Matched private (val provenSet: Set[Atom], reverseOrdered: List[Atom]) {
final class Matched private (val provenSet: Set[Atom], reverseOrdered: List[Atom]):
def add(atoms: Set[Atom]): Matched = add(atoms.toList)
def add(atoms: List[Atom]): Matched = {
def add(atoms: List[Atom]): Matched =
val newOnly = atoms.filterNot(provenSet)
new Matched(provenSet ++ newOnly.toSet, newOnly ::: reverseOrdered)
}
def ordered: List[Atom] = reverseOrdered.reverse
override def toString = ordered.map(_.label).mkString("Matched(", ",", ")")
}
object Matched {
object Matched:
val empty = new Matched(Set.empty, Nil)
}
/** Separates a sequence of literals into `(pos, neg)` atom sequences. */
private def separate(lits: Seq[Literal]): (Seq[Atom], Seq[Atom]) =
@@ -197,10 +180,9 @@ object Logic {
* Finds clauses that have no body and thus prove their head. Returns `(<proven atoms>, <remaining
* unproven clauses>)`.
*/
private def findProven(c: Clauses): (Set[Atom], List[Clause]) = {
private def findProven(c: Clauses): (Set[Atom], List[Clause]) =
val (proven, unproven) = c.clauses.partition(_.body == True)
(proven.flatMap(_.head).toSet, unproven)
}
private def keepPositive(lits: Set[Literal]): Set[Atom] =
lits.collect { case a: Atom => a }
@@ -211,28 +193,26 @@ object Logic {
factsToProcess: Set[Literal],
state: Matched
): Matched =
applyAll(clauses, factsToProcess) match {
applyAll(clauses, factsToProcess) match
case None => state // all of the remaining clauses failed on the new facts
case Some(applied) =>
val (proven, unprovenClauses) = findProven(applied)
val processedFacts = state.add(keepPositive(factsToProcess))
val newlyProven = proven -- processedFacts.provenSet
val newState = processedFacts.add(newlyProven)
if (unprovenClauses.isEmpty) newState // no remaining clauses, done.
else {
if unprovenClauses.isEmpty then newState // no remaining clauses, done.
else
val unproven = Clauses(unprovenClauses)
val nextFacts: Set[Literal] =
if (newlyProven.nonEmpty) newlyProven.toSet[Literal]
if newlyProven.nonEmpty then newlyProven.toSet[Literal]
else inferFailure(unproven)
reduce0(unproven, nextFacts, newState)
}
}
/**
* Finds negated atoms under the negation as failure rule and returns them. This should be called
* only after there are no more known atoms to be substituted.
*/
private def inferFailure(clauses: Clauses): Set[Literal] = {
private def inferFailure(clauses: Clauses): Set[Literal] =
/* At this point, there is at least one clause and one of the following is the case as the
result of the acyclic negation rule:
i. there is at least one variable that occurs in a clause body but not in the head of a
@@ -245,16 +225,13 @@ object Logic {
*/
val allAtoms = atoms(clauses)
val newFacts: Set[Literal] = negated(allAtoms.triviallyFalse)
if (newFacts.nonEmpty) newFacts
else {
if newFacts.nonEmpty then newFacts
else
val possiblyTrue = hasNegatedDependency(clauses.clauses, Relation.empty, Relation.empty)
val newlyFalse: Set[Literal] = negated(allAtoms.inHead -- possiblyTrue)
if (newlyFalse.nonEmpty)
newlyFalse
if newlyFalse.nonEmpty then newlyFalse
else // should never happen due to the acyclic negation rule
sys.error(s"No progress:\n\tclauses: $clauses\n\tpossibly true: $possiblyTrue")
}
}
private def negated(atoms: Set[Atom]): Set[Literal] = atoms.map(a => (Negated(a): Literal))
@@ -269,7 +246,7 @@ object Logic {
posDeps: Relation[Atom, Atom],
negDeps: Relation[Atom, Atom]
): List[Atom] =
clauses match {
clauses match
case Seq() =>
// because cycles between positive literals are allowed, this isn't strictly a topological sort
Dag.topologicalSortUnchecked(negDeps._1s)(posDeps.reverse)
@@ -281,7 +258,6 @@ object Logic {
(pdeps.+(d, pos), ndeps.+(d, neg))
}
hasNegatedDependency(tail, newPos, newNeg)
}
/** Computes the `(positive, negative)` literals in `formula`. */
private def directDeps(formula: Formula): (Seq[Atom], Seq[Atom]) =
@@ -290,27 +266,25 @@ object Logic {
case a: Atom => Left(a)
}
private def literals(formula: Formula): Set[Literal] = formula match {
private def literals(formula: Formula): Set[Literal] = formula match
case And(lits) => lits
case l: Literal => Set(l)
case True => Set.empty
}
/** Computes the atoms in the heads and bodies of the clauses in `clause`. */
def atoms(cs: Clauses): Atoms = cs.clauses.map(c => Atoms(c.head, atoms(c.body))).reduce(_ ++ _)
/** Computes the set of all atoms in `formula`. */
def atoms(formula: Formula): Set[Atom] = formula match {
def atoms(formula: Formula): Set[Atom] = formula match
case And(lits) => lits.map(_.atom)
case Negated(lit) => Set(lit)
case a: Atom => Set(a)
case True => Set()
}
/**
* Represents the set of atoms in the heads of clauses and in the bodies (formulas) of clauses.
*/
final case class Atoms(inHead: Set[Atom], inFormula: Set[Atom]) {
final case class Atoms(inHead: Set[Atom], inFormula: Set[Atom]):
/** Concatenates this with `as`. */
def ++(as: Atoms): Atoms = Atoms(inHead ++ as.inHead, inFormula ++ as.inFormula)
@@ -318,8 +292,6 @@ object Logic {
/** Atoms that cannot be true because they do not occur in a head. */
def triviallyFalse: Set[Atom] = inFormula -- inHead
}
/**
* Applies known facts to `clause`s, deriving a new, possibly empty list of clauses.
* 1. If a fact is in the body of a clause, the derived clause has that fact removed from the
@@ -333,39 +305,33 @@ object Logic {
* Postcondition: no atom in `facts` is present in the result Postcondition: No clauses have an
* empty head
*/
def applyAll(cs: Clauses, facts: Set[Literal]): Option[Clauses] = {
def applyAll(cs: Clauses, facts: Set[Literal]): Option[Clauses] =
val newClauses =
if (facts.isEmpty)
if facts.isEmpty then
cs.clauses.filter(_.head.nonEmpty) // still need to drop clauses with an empty head
else
cs.clauses.map(c => applyAll(c, facts)).flatMap(_.toList)
if (newClauses.isEmpty) None else Some(Clauses(newClauses))
}
else cs.clauses.map(c => applyAll(c, facts)).flatMap(_.toList)
if newClauses.isEmpty then None else Some(Clauses(newClauses))
def applyAll(c: Clause, facts: Set[Literal]): Option[Clause] = {
def applyAll(c: Clause, facts: Set[Literal]): Option[Clause] =
val atoms = facts.map(_.atom)
val newHead = c.head -- atoms // 3.
if (newHead.isEmpty) // 4. empty head
if newHead.isEmpty then // 4. empty head
None
else
substitute(c.body, facts).map(f => Clause(f, newHead)) // 1, 2
}
else substitute(c.body, facts).map(f => Clause(f, newHead)) // 1, 2
/** Derives the formula that results from substituting `facts` into `formula`. */
@tailrec def substitute(formula: Formula, facts: Set[Literal]): Option[Formula] = formula match {
@tailrec def substitute(formula: Formula, facts: Set[Literal]): Option[Formula] = formula match
case And(lits) =>
def negated(lits: Set[Literal]): Set[Literal] = lits.map(a => !a)
if (lits.exists(negated(facts))) // 2.
if lits.exists(negated(facts)) then // 2.
None
else {
else
val newLits = lits -- facts
val newF =
if (newLits.isEmpty) (True: Formula)
if newLits.isEmpty then (True: Formula)
else (And(newLits): Formula)
Some(newF) // 1.
}
case True => Some(True)
case lit: Literal => // define in terms of And
substitute(And(Set(lit)), facts)
}
}
end Logic
@@ -13,7 +13,7 @@ import org.scalacheck.*
import Prop.secure
import Logic.{ LogicException, Matched }
object LogicTest extends Properties("Logic") {
object LogicTest extends Properties("Logic"):
import TestClauses.*
property("Handles trivial resolution.") = secure(expect(trivial, Set(A)))
@@ -34,18 +34,16 @@ object LogicTest extends Properties("Logic") {
)
*/
def expect(result: Either[LogicException, Matched], expected: Set[Atom]) = result match {
def expect(result: Either[LogicException, Matched], expected: Set[Atom]) = result match
case Left(_) => false
case Right(res) =>
val actual = res.provenSet
if (actual != expected)
if actual != expected then
sys.error(s"Expected to prove $expected, but actually proved $actual")
else
true
}
}
else true
end LogicTest
object TestClauses {
object TestClauses:
val A = Atom("A")
val B = Atom("B")
@@ -71,7 +69,7 @@ object TestClauses {
A.proves(D) ::
clauses
val excludedNeg = {
val excludedNeg =
val cs =
(!A).proves(B) ::
Nil
@@ -80,9 +78,8 @@ object TestClauses {
(!B) ::
Nil
Logic.reduceAll(cs, init.toSet)
}
val excludedPos = {
val excludedPos =
val cs =
A.proves(B) ::
Nil
@@ -91,39 +88,34 @@ object TestClauses {
(!B) ::
Nil
Logic.reduceAll(cs, init.toSet)
}
val trivial = {
val trivial =
val cs =
Formula.True.proves(A) ::
Nil
Logic.reduceAll(cs, Set.empty)
}
val lessTrivial = {
val lessTrivial =
val cs =
Formula.True.proves(A) ::
Formula.True.proves(B) ::
(A && B && (!C)).proves(D) ::
Nil
Logic.reduceAll(cs, Set())
}
val ordering = {
val ordering =
val cs =
E.proves(F) ::
(C && !D).proves(E) ::
(A && B).proves(C) ::
Nil
Logic.reduceAll(cs, Set(A, B))
}
def all(): Unit = {
def all(): Unit =
println(s"Cycles: $cycles")
println(s"xNeg: $excludedNeg")
println(s"xPos: $excludedPos")
println(s"trivial: $trivial")
println(s"lessTrivial: $lessTrivial")
println(s"ordering: $ordering")
}
}
end TestClauses
@@ -9,7 +9,7 @@ package sbt.internal.util
import scala.quoted.{ Expr, Quotes, quotes }
abstract class SourcePositionImpl {
abstract class SourcePositionImpl:
/**
* Creates a SourcePosition by using the enclosing position of the invocation of this method.
@@ -18,11 +18,10 @@ abstract class SourcePositionImpl {
*/
inline def fromEnclosing(): SourcePosition =
${ SourcePositionImpl.fromEnclosingImpl }
}
object SourcePositionImpl {
object SourcePositionImpl:
def fromEnclosingImpl(using Quotes): Expr[SourcePosition] = {
def fromEnclosingImpl(using Quotes): Expr[SourcePosition] =
val pos = quotes.reflect.Position.ofMacroExpansion
if pos.startLine >= 0 then
'{
@@ -32,5 +31,3 @@ object SourcePositionImpl {
).withSourceCode(${ Expr(pos.sourceCode) })
}
else '{ NoPosition }
}
}
@@ -10,15 +10,14 @@ package sbt.internal.util
sealed trait SourcePosition
sealed trait FilePosition extends SourcePosition {
sealed trait FilePosition extends SourcePosition:
def path: String
def startLine: Int
def sourceCode: Option[String]
}
case object NoPosition extends SourcePosition
final case class LinePosition(path: String, startLine: Int) extends FilePosition {
final case class LinePosition(path: String, startLine: Int) extends FilePosition:
private var _sourceCode: Option[String] = None
def sourceCode: Option[String] = _sourceCode
def withSourceCode(code: String): LinePosition =
@@ -29,13 +28,11 @@ final case class LinePosition(path: String, startLine: Int) extends FilePosition
c match
case Some(code) => this.withSourceCode(code)
case None => this
}
final case class LineRange(start: Int, end: Int) {
final case class LineRange(start: Int, end: Int):
def shift(n: Int) = new LineRange(start + n, end + n)
}
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
final case class RangePosition(path: String, range: LineRange) extends FilePosition:
private var _sourceCode: Option[String] = None
def startLine = range.start
def sourceCode: Option[String] = _sourceCode
@@ -47,6 +44,5 @@ final case class RangePosition(path: String, range: LineRange) extends FilePosit
c match
case Some(code) => this.withSourceCode(code)
case None => this
}
object SourcePosition extends SourcePositionImpl
@@ -17,7 +17,7 @@ object SourcePositionSpec extends Properties:
"SourcePosition() should return a SourcePosition", {
val filename = "SourcePositionSpec.scala"
val lineNumber = 19
SourcePosition.fromEnclosing() match {
SourcePosition.fromEnclosing() match
case pos @ LinePosition(path, startLine) =>
Result.assert(path == filename && startLine == lineNumber).log(pos.toString())
Result
@@ -26,7 +26,6 @@ object SourcePositionSpec extends Properties:
case pos @ RangePosition(path, range) =>
Result.assert(path == filename && inRange(range, lineNumber)).log(pos.toString())
case NoPosition => Result.assert(false).log("No source position found")
}
}
)
)
@@ -10,7 +10,7 @@ package sbt.internal.util
import Relation.*
object Relation {
object Relation:
/** Constructs a new immutable, finite relation that is initially empty. */
def empty[A, B]: Relation[A, B] = make(Map.empty, Map.empty)
@@ -27,24 +27,22 @@ object Relation {
* Constructs a relation such that for every entry `_1 -> _2s` in `forward` and every `_2` in
* `_2s`, `(_1, _2)` is in the relation.
*/
def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] = {
val reversePairs = for ((a, bs) <- forward.view; b <- bs.view) yield (b, a)
def reconstruct[A, B](forward: Map[A, Set[B]]): Relation[A, B] =
val reversePairs = for (a, bs) <- forward.view; b <- bs.view yield (b, a)
val reverse = reversePairs.foldLeft(Map.empty[B, Set[A]]) { case (m, (b, a)) =>
add(m, b, a :: Nil)
}
make(forward filter { (a, bs) => bs.nonEmpty }, reverse)
}
def merge[A, B](rels: Iterable[Relation[A, B]]): Relation[A, B] =
rels.foldLeft(Relation.empty[A, B])(_ ++ _)
private[sbt] def remove[X, Y](map: M[X, Y], from: X, to: Y): M[X, Y] =
map.get(from) match {
map.get(from) match
case Some(tos) =>
val newSet = tos - to
if (newSet.isEmpty) map - from else map.updated(from, newSet)
if newSet.isEmpty then map - from else map.updated(from, newSet)
case None => map
}
private[sbt] def combine[X, Y](a: M[X, Y], b: M[X, Y]): M[X, Y] =
b.foldLeft(a)((map, mapping) => add(map, mapping._1, mapping._2))
@@ -57,7 +55,7 @@ object Relation {
private[sbt] type M[X, Y] = Map[X, Set[Y]]
/** when both parameters taken by relation are the same type, switch calls a function on them. */
private[sbt] def switch[X, Y](relation: Relation[X, X], f: X => Y): Relation[Y, Y] = {
private[sbt] def switch[X, Y](relation: Relation[X, X], f: X => Y): Relation[Y, Y] =
val forward = relation.forwardMap.map { (first, second) =>
f(first) -> second.map(f)
}
@@ -65,11 +63,10 @@ object Relation {
f(first) -> second.map(f)
}
make(forward, reverse)
}
}
end Relation
/** Binary relation between A and B. It is a set of pairs (_1, _2) for _1 in A, _2 in B. */
trait Relation[A, B] {
trait Relation[A, B]:
/** Returns the set of all `_2`s such that `(_1, _2)` is in this relation. */
def forward(_1: A): Set[B]
@@ -153,11 +150,11 @@ trait Relation[A, B] {
* is in this relation.
*/
def reverseMap: Map[B, Set[A]]
}
end Relation
// Note that we assume without checking that fwd and rev are consistent.
private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]])
extends Relation[A, B] {
extends Relation[A, B]:
def forwardMap = fwd
def reverseMap = rev
@@ -175,7 +172,7 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]])
def +(pair: (A, B)) = this + (pair._1, Set(pair._2))
def +(from: A, to: B) = this + (from, to :: Nil)
def +(from: A, to: Iterable[B]) =
if (to.isEmpty) this
if to.isEmpty then this
else new MRelation(add(fwd, from, to), to.foldLeft(rev)((map, t) => add(map, t, from :: Nil)))
def ++(rs: Iterable[(A, B)]) = rs.foldLeft(this: Relation[A, B]) { _ + _ }
@@ -191,35 +188,32 @@ private final class MRelation[A, B](fwd: Map[A, Set[B]], rev: Map[B, Set[A]])
new MRelation(remove(fwd, pair._1, pair._2), remove(rev, pair._2, pair._1))
def -(t: A): Relation[A, B] =
fwd.get(t) match {
fwd.get(t) match
case Some(rs) =>
val upRev = rs.foldLeft(rev)((map, r) => remove(map, r, t))
new MRelation(fwd - t, upRev)
case None => this
}
def filter(f: (A, B) => Boolean): Relation[A, B] = Relation.empty[A, B] ++ all.filter(f.tupled)
def partition(f: (A, B) => Boolean): (Relation[A, B], Relation[A, B]) = {
def partition(f: (A, B) => Boolean): (Relation[A, B], Relation[A, B]) =
val (y, n) = all.partition(f.tupled)
(Relation.empty[A, B] ++ y, Relation.empty[A, B] ++ n)
}
def groupBy[K](discriminator: ((A, B)) => K): Map[K, Relation[A, B]] =
all.groupBy(discriminator).view.mapValues { Relation.empty[A, B] ++ _ }.toMap
def contains(a: A, b: B): Boolean = forward(a)(b)
override def equals(other: Any) = other match {
override def equals(other: Any) = other match
// We assume that the forward and reverse maps are consistent, so we only use the forward map
// for equality. Note that key -> Empty is semantically the same as key not existing.
case o: MRelation[?, ?] =>
forwardMap.filterNot(_._2.isEmpty) == o.forwardMap.filterNot(_._2.isEmpty)
case _ => false
}
override def hashCode = fwd.filterNot(_._2.isEmpty).hashCode()
override def toString =
all.map { (a, b) => s"$a -> $b" }.mkString("Relation [", ", ", "]")
}
end MRelation
@@ -11,12 +11,12 @@ package sbt.internal.util
import org.scalacheck.*
import Prop.*
object RelationTest extends Properties("Relation") {
object RelationTest extends Properties("Relation"):
property("Added entry check") = forAll { (pairs: List[(Int, Double)]) =>
val r = Relation.empty[Int, Double] ++ pairs
check(r, pairs)
}
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) = {
def check(r: Relation[Int, Double], pairs: Seq[(Int, Double)]) =
val _1s = pairs.map(_._1).toSet
val _2s = pairs.map(_._2).toSet
@@ -28,7 +28,6 @@ object RelationTest extends Properties("Relation") {
(r.forwardMap(a) contains b) &&
(r.reverseMap(b) contains a)
}
}
property("Does not contain removed entries") = forAll { (pairs: List[(Int, Double, Boolean)]) =>
val add = pairs.map { (a, b, c) => (a, b) }
@@ -71,10 +70,10 @@ object RelationTest extends Properties("Relation") {
}
def all[T](s: Seq[T])(p: T => Prop): Prop =
if (s.isEmpty) true else s.map(p).reduceLeft(_ && _)
}
if s.isEmpty then true else s.map(p).reduceLeft(_ && _)
end RelationTest
object EmptyRelationTest extends Properties("Empty relation") {
object EmptyRelationTest extends Properties("Empty relation"):
lazy val e = Relation.empty[Int, Double]
property("Forward empty") = forAll((i: Int) => e.forward(i).isEmpty)
@@ -83,4 +82,3 @@ object EmptyRelationTest extends Properties("Empty relation") {
property("Reverse map empty") = e.reverseMap.isEmpty
property("_1 empty") = e._1s.isEmpty
property("_2 empty") = e._2s.isEmpty
}
@@ -10,6 +10,5 @@ package sbt
package internal
package scripted
object CommentHandler extends BasicStatementHandler {
object CommentHandler extends BasicStatementHandler:
def apply(command: String, args: List[String]) = ()
}
@@ -16,7 +16,7 @@ import sbt.io.{ IO, Path }
import sbt.io.syntax.*
import Path.*
class FileCommands(baseDirectory: File) extends BasicStatementHandler {
class FileCommands(baseDirectory: File) extends BasicStatementHandler:
final val OR = "||"
lazy val view = FileTreeView.Ops(FileTreeView.default)
val baseGlob = Glob(baseDirectory)
@@ -46,10 +46,9 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
)
def apply(command: String, arguments: List[String]): Unit =
commands.get(command).map(_(arguments)) match {
commands.get(command).map(_(arguments)) match
case Some(_) => ()
case _ => scriptError("Unknown command " + command); ()
}
def scriptError(message: String): Unit = sys.error("Test script error: " + message)
def spaced[T](l: Seq[T]) = l.mkString(" ")
@@ -77,6 +76,7 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
List(combined)
if exprs.contains("||") then orGlobs
else globs(exprs)
end filterFromStrings
def touch(paths: List[String]): Unit = IO.touch(fromStrings(paths))
def delete(paths: List[String]): Unit =
@@ -92,47 +92,37 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
IO.copyFile(fromString(from), fromString(to))
def makeDirectories(paths: List[String]) =
IO.createDirectories(fromStrings(paths))
def diffFiles(file1: String, file2: String): Unit = {
def diffFiles(file1: String, file2: String): Unit =
val lines1 = IO.readLines(fromString(file1))
val lines2 = IO.readLines(fromString(file2))
if (lines1 != lines2)
if lines1 != lines2 then
scriptError(
"File contents are different:\n" + lines1.mkString("\n") +
"\nAnd:\n" + lines2.mkString("\n")
)
}
def newer(a: String, b: String): Unit = {
def newer(a: String, b: String): Unit =
val pathA = fromString(a)
val pathB = fromString(b)
val isNewer = pathA.exists &&
(!pathB.exists || IO.getModifiedTimeOrZero(pathA) > IO.getModifiedTimeOrZero(pathB))
if (!isNewer) {
scriptError(s"$pathA is not newer than $pathB")
}
}
if !isNewer then scriptError(s"$pathA is not newer than $pathB")
// use FileTreeView to test if a file with the given filter exists
def exists0(filter: PathFilter): Boolean =
view.list(baseGlob / RecursiveGlob, filter).nonEmpty
def exists(paths: List[String]): Unit = {
def exists(paths: List[String]): Unit =
val notPresent = filterFromStrings(paths).filter(!exists0(_))
if (notPresent.nonEmpty)
if notPresent.nonEmpty then
scriptError("File(s) did not exist: " + notPresent.mkString("[ ", " , ", " ]"))
}
def absent(paths: List[String]): Unit = {
def absent(paths: List[String]): Unit =
val present = filterFromStrings(paths).filter(exists0)
if (present.nonEmpty)
scriptError("File(s) existed: " + present.mkString("[ ", " , ", " ]"))
}
if present.nonEmpty then scriptError("File(s) existed: " + present.mkString("[ ", " , ", " ]"))
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.")
else {
def execute0(command: String, args: List[String]): Unit =
if command.trim.isEmpty then scriptError("Command was empty.")
else
val exitValue = sys.process.Process(command :: args, baseDirectory).!
if (exitValue != 0)
sys.error("Nonzero exit value (" + exitValue + ")")
}
}
if exitValue != 0 then sys.error("Nonzero exit value (" + exitValue + ")")
type NamedCommand = (String, List[String] => Unit)
@@ -140,10 +130,8 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
extension (commandName: String)
def nonEmpty(action: List[String] => Unit): NamedCommand =
commandName -> { paths =>
if (paths.isEmpty)
scriptError("No arguments specified for " + commandName + " command.")
else
action(paths)
if paths.isEmpty then scriptError("No arguments specified for " + commandName + " command.")
else action(paths)
}
def twoArg(requiredArgs: String, action: (String, String) => Unit): NamedCommand =
commandName -> {
@@ -181,4 +169,5 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler {
"Wrong number of arguments to " + commandName + " command. " +
requiredArgs + " required, found: '" + spaced(args) + "'."
)
}
end extension
end FileCommands
@@ -13,14 +13,11 @@ package scripted
import java.util as ju
import java.net.URL
final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent) {
final class FilteredLoader(parent: ClassLoader) extends ClassLoader(parent):
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[?] = {
if (className.startsWith("java.") || className.startsWith("javax."))
override final def loadClass(className: String, resolve: Boolean): Class[?] =
if className.startsWith("java.") || className.startsWith("javax.") then
super.loadClass(className, resolve)
else
throw new ClassNotFoundException(className)
}
else throw new ClassNotFoundException(className)
override def getResources(name: String): ju.Enumeration[URL] = null
override def getResource(name: String): URL = null
}
@@ -8,6 +8,5 @@
package sbt.internal.scripted
trait HandlersProvider {
trait HandlersProvider:
def getHandlers(config: ScriptConfig): Map[Char, StatementHandler]
}
@@ -13,46 +13,35 @@ package scripted
final class TestException(statement: Statement, msg: String, exception: Throwable)
extends RuntimeException(statement.linePrefix + " " + msg, exception)
class ScriptRunner {
class ScriptRunner:
import scala.collection.mutable.HashMap
def apply(statements: List[(StatementHandler, Statement)]): Unit = {
def apply(statements: List[(StatementHandler, Statement)]): Unit =
val states = new HashMap[StatementHandler, Any]
def processStatement(handler: StatementHandler, statement: Statement): Unit = {
def processStatement(handler: StatementHandler, statement: Statement): Unit =
val state = states(handler).asInstanceOf[handler.State]
val nextState =
try {
Right(handler(statement.command, statement.arguments, state))
} catch {
case e: Exception => Left(e)
}
nextState match {
try Right(handler(statement.command, statement.arguments, state))
catch case e: Exception => Left(e)
nextState match
case Left(err) =>
if (statement.successExpected) {
err match {
if statement.successExpected then
err match
case t: TestFailed =>
throw new TestException(statement, "Command failed: " + t.getMessage, null)
case _ => throw new TestException(statement, "Command failed", err)
}
} else
()
else ()
case Right(s) =>
if (statement.successExpected)
states(handler) = s
if statement.successExpected then states(handler) = s
else
throw new TestException(statement, "Command succeeded but failure was expected", null)
}
}
val handlers = Set() ++ statements.map(_._1)
try {
try
handlers.foreach(handler => states(handler) = handler.initialState)
statements foreach (Function.tupled(processStatement))
} finally {
for (handler <- handlers; state <- states.get(handler)) {
try {
handler.finish(state.asInstanceOf[handler.State])
} catch { case e: Exception => () }
}
}
}
}
finally
for handler <- handlers; state <- states.get(handler) do
try handler.finish(state.asInstanceOf[handler.State])
catch case e: Exception => ()
end apply
end ScriptRunner
@@ -19,13 +19,13 @@ import sbt.io.syntax.*
import sbt.internal.io.Resources
import java.util.concurrent.atomic.AtomicInteger
object ScriptedRunnerImpl {
object ScriptedRunnerImpl:
def run(
resourceBaseDirectory: File,
bufferLog: Boolean,
tests: Array[String],
handlersProvider: HandlersProvider
): Unit = {
): Unit =
val context = LoggerContext()
val runner = new ScriptedTests(resourceBaseDirectory, bufferLog, handlersProvider)
val logger = newLogger(context)
@@ -33,38 +33,32 @@ object ScriptedRunnerImpl {
case ScriptedTest(group, name) =>
runner.scriptedTest(group, name, logger, context)
}
if (tests.nonEmpty && allTests.isEmpty) {
if tests.nonEmpty && allTests.isEmpty then
sys.error(s"No tests found matching: ${tests.mkString(", ")}")
}
runAll(allTests)
}
def runAll(tests: Seq[() => Option[String]]): Unit = {
val errors = for (test <- tests; err <- test()) yield err
if (errors.nonEmpty)
sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
}
def runAll(tests: Seq[() => Option[String]]): Unit =
val errors = for test <- tests; err <- test() yield err
if errors.nonEmpty then sys.error(errors.mkString("Failed tests:\n\t", "\n\t", "\n"))
def get(tests: Seq[String], baseDirectory: File, log: ManagedLogger): Seq[ScriptedTest] =
if (tests.isEmpty) listTests(baseDirectory, log) else parseTests(tests)
if tests.isEmpty then listTests(baseDirectory, log) else parseTests(tests)
def listTests(baseDirectory: File, log: ManagedLogger): Seq[ScriptedTest] =
(new ListTests(baseDirectory, _ => true, log)).listTests
def parseTests(in: Seq[String]): Seq[ScriptedTest] =
for (testString <- in) yield {
for testString <- in yield
val Array(group, name) = testString.split("/").map(_.trim)
ScriptedTest(group, name)
}
private[sbt] val generateId: AtomicInteger = new AtomicInteger
private[sbt] def newLogger(context: LoggerContext): ManagedLogger = {
private[sbt] def newLogger(context: LoggerContext): ManagedLogger =
val loggerName = "scripted-" + generateId.incrementAndGet
context.logger(loggerName, None, None)
}
}
end ScriptedRunnerImpl
final class ScriptedTests(
resourceBaseDirectory: File,
bufferLog: Boolean,
handlersProvider: HandlersProvider,
stripQuotes: Boolean
) {
):
def this(resourceBaseDirectory: File, bufferLog: Boolean, handlersProvider: HandlersProvider) =
this(resourceBaseDirectory, bufferLog, handlersProvider, true)
private val testResources = new Resources(resourceBaseDirectory)
@@ -105,29 +99,23 @@ final class ScriptedTests(
prescripted: File => Unit,
log: ManagedLogger,
context: LoggerContext,
): Seq[() => Option[String]] = {
for (groupDir <- (resourceBaseDirectory * group).get(); nme <- (groupDir * name).get()) yield {
): Seq[() => Option[String]] =
for groupDir <- (resourceBaseDirectory * group).get(); nme <- (groupDir * name).get() yield
val g = groupDir.getName
val n = nme.getName
val str = s"$g / $n"
() => {
() =>
println("Running " + str)
testResources.readWriteResourceDirectory(g, n) { testDirectory =>
val disabled = new File(testDirectory, "disabled").isFile
if (disabled) {
if disabled then
log.info("D " + str + " [DISABLED]")
None
} else {
try {
else
try
scriptedTest(str, testDirectory, prescripted, log, context); None
} catch {
case _: TestException | _: PendingTestSuccessException => Some(str)
}
}
catch case _: TestException | _: PendingTestSuccessException => Some(str)
}
}
}
}
private def scriptedTest(
label: String,
@@ -135,104 +123,88 @@ final class ScriptedTests(
prescripted: File => Unit,
log: ManagedLogger,
context: LoggerContext,
): Unit = {
): Unit =
val buffered = BufferedAppender(appender)
context.clearAppenders(log.name)
context.addAppender(log.name, (buffered -> Level.Debug))
if (bufferLog) {
buffered.record()
}
def createParser() = {
if bufferLog then buffered.record()
def createParser() =
// val fileHandler = new FileCommands(testDirectory)
// // val sbtHandler = new SbtHandler(testDirectory, launcher, buffered, launchOpts)
// new TestScriptParser(Map('$' -> fileHandler, /* '>' -> sbtHandler, */ '#' -> CommentHandler))
val scriptConfig = new ScriptConfig(label, testDirectory, log)
new TestScriptParser(handlersProvider.getHandlers(scriptConfig))
}
val (file, pending) = {
val (file, pending) =
val normal = new File(testDirectory, ScriptFilename)
val pending = new File(testDirectory, PendingScriptFilename)
if (pending.isFile) (pending, true) else (normal, false)
}
val pendingString = if (pending) " [PENDING]" else ""
if pending.isFile then (pending, true) else (normal, false)
val pendingString = if pending then " [PENDING]" else ""
def runTest(): Unit = {
def runTest(): Unit =
val run = new ScriptRunner
val parser = createParser()
run(parser.parse(file, stripQuotes))
}
def testFailed(): Unit = {
if (pending) buffered.clearBuffer() else buffered.stopBuffer()
def testFailed(): Unit =
if pending then buffered.clearBuffer() else buffered.stopBuffer()
log.error("x " + label + pendingString)
}
try {
try
prescripted(testDirectory)
runTest()
log.info("+ " + label + pendingString)
if (pending) throw new PendingTestSuccessException(label)
} catch {
if pending then throw new PendingTestSuccessException(label)
catch
case e: TestException =>
testFailed()
e.getCause match {
e.getCause match
case null | _: java.net.SocketException => log.error(" " + e.getMessage)
case _ => if (!pending) e.printStackTrace
}
if (!pending) throw e
case _ => if !pending then e.printStackTrace
if !pending then throw e
case e: PendingTestSuccessException =>
testFailed()
log.error(" Mark as passing to remove this failure.")
throw e
case e: Exception =>
testFailed()
if (!pending) throw e
} finally {
buffered.clearBuffer()
}
}
}
if !pending then throw e
finally buffered.clearBuffer()
end scriptedTest
end ScriptedTests
// object ScriptedTests extends ScriptedRunner {
// val emptyCallback: File => Unit = { _ => () }
// }
final case class ScriptedTest(group: String, name: String) {
final case class ScriptedTest(group: String, name: String):
override def toString = group + "/" + name
}
object ListTests {
object ListTests:
def list(directory: File, filter: java.io.FileFilter): Seq[File] =
wrapNull(directory.listFiles(filter)).toSeq
}
import ListTests.*
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger) {
final class ListTests(baseDirectory: File, accept: ScriptedTest => Boolean, log: Logger):
def filter = DirectoryFilter -- HiddenFileFilter
def listTests: Seq[ScriptedTest] = {
def listTests: Seq[ScriptedTest] =
list(baseDirectory, filter) flatMap { group =>
val groupName = group.getName
listTests(group).map(ScriptedTest(groupName, _))
}
}
private def listTests(group: File): Seq[String] = {
private def listTests(group: File): Seq[String] =
val groupName = group.getName
val allTests = list(group, filter).sortBy(_.getName)
if (allTests.isEmpty) {
if allTests.isEmpty then
log.warn("No tests in test group " + groupName)
Seq.empty
} else {
else
val (included, skipped) =
allTests.toList.partition(test => accept(ScriptedTest(groupName, test.getName)))
if (included.isEmpty) log.warn("Test group " + groupName + " skipped.")
else if (skipped.nonEmpty) {
if included.isEmpty then log.warn("Test group " + groupName + " skipped.")
else if skipped.nonEmpty then
log.warn("Tests skipped in group " + group.getName + ":")
skipped.foreach(testName => log.warn(" " + testName.getName))
}
Seq(included.map(_.getName)*)
}
}
}
end ListTests
class PendingTestSuccessException(label: String) extends Exception {
class PendingTestSuccessException(label: String) extends Exception:
override def getMessage: String =
s"The pending test $label succeeded. Mark this test as passing to remove this failure."
}
@@ -10,14 +10,13 @@ package sbt
package internal
package scripted
trait StatementHandler {
trait StatementHandler:
type State
def initialState: State
def apply(command: String, arguments: List[String], state: State): State
def finish(state: State): Unit
}
trait BasicStatementHandler extends StatementHandler {
trait BasicStatementHandler extends StatementHandler:
final type State = Unit
final def initialState: State = ()
@@ -26,9 +25,7 @@ trait BasicStatementHandler extends StatementHandler {
def apply(command: String, arguments: List[String]): Unit
def finish(state: Unit) = ()
}
/** Use when a stack trace is not useful */
final class TestFailed(msg: String) extends RuntimeException(msg) {
final class TestFailed(msg: String) extends RuntimeException(msg):
override def fillInStackTrace: Throwable = this
}
@@ -30,25 +30,22 @@ final case class Statement(
arguments: List[String],
successExpected: Boolean,
line: Int
) {
):
def linePrefix = "{line " + line + "} "
}
private object TestScriptParser {
private object TestScriptParser:
val SuccessLiteral = "success"
val FailureLiteral = "failure"
val WordRegex = """[^ \[\]\s'\"][^ \[\]\s]*""".r
}
import TestScriptParser.*
class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParsers {
class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParsers:
require(handlers.nonEmpty)
override def skipWhitespace = false
import IO.read
if (handlers.keys.exists(isWhitespace))
sys.error("Start characters cannot be whitespace")
if (handlers.keys.exists(key => key == '+' || key == '-'))
if handlers.keys.exists(isWhitespace) then sys.error("Start characters cannot be whitespace")
if handlers.keys.exists(key => key == '+' || key == '-') then
sys.error("Start characters cannot be '+' or '-'")
@deprecated("Use variant that specifies whether to strip quotes or not", "1.4.0")
@@ -65,15 +62,12 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
script: String,
label: Option[String],
stripQuotes: Boolean
): List[(StatementHandler, Statement)] = {
parseAll(statements(stripQuotes), script) match {
): List[(StatementHandler, Statement)] =
parseAll(statements(stripQuotes), script) match
case Success(result, next) => result
case err: NoSuccess => {
case err: NoSuccess =>
val labelString = label.map("'" + _ + "' ").getOrElse("")
sys.error("Could not parse test script, " + labelString + err.toString)
}
}
}
@deprecated("Use variant that specifies whether to strip quotes or not", "1.4.0")
lazy val statements = rep1(space ~> statement <~ newline)
@@ -82,23 +76,20 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
@deprecated("Use variant that specifies whether to strip quotes or not", "1.4.0")
def statement: Parser[(StatementHandler, Statement)] = statement(stripQuotes = true)
def statement(stripQuotes: Boolean): Parser[(StatementHandler, Statement)] = {
trait PositionalStatement extends Positional {
def statement(stripQuotes: Boolean): Parser[(StatementHandler, Statement)] =
trait PositionalStatement extends Positional:
def tuple: (StatementHandler, Statement)
}
positioned {
val w = if (stripQuotes) word else rawWord
val w = if stripQuotes then word else rawWord
val command = w | err("expected command")
val arguments = rep(space ~> w | failure("expected argument"))
(successParser ~ (space ~> startCharacterParser <~ space) ~! command ~! arguments) ^^ {
case successExpected ~ start ~ command ~ arguments =>
new PositionalStatement {
new PositionalStatement:
def tuple =
(handlers(start), new Statement(command, arguments, successExpected, pos.line))
}
}
} ^^ (_.tuple)
}
def successParser: Parser[Boolean] = ('+' ^^^ true) | ('-' ^^^ false) | success(true)
def space: Parser[String] = """[ \t]*""".r
@@ -116,4 +107,4 @@ class TestScriptParser(handlers: Map[Char, StatementHandler]) extends RegexParse
)
def newline = """\s*([\n\r]|$)""".r
}
end TestScriptParser
+50 -81
View File
@@ -6,48 +6,43 @@ import DebianConstants.*
import Dependencies.*
lazy val sbtOfflineInstall =
sys.props.getOrElse("sbt.build.offline", sys.env.getOrElse("sbt.build.offline", "false")) match {
sys.props.getOrElse("sbt.build.offline", sys.env.getOrElse("sbt.build.offline", "false")) match
case "true" | "1" => true
case "false" | "0" => false
case _ => false
}
lazy val sbtIncludeSbtn =
sys.props.getOrElse(
"sbt.build.includesbtn",
sys.env.getOrElse("sbt.build.includesbtn", "true")
) match {
) match
case "true" | "1" => true
case "false" | "0" => false
case _ => false
}
lazy val sbtIncludeSbtLaunch =
sys.props.getOrElse(
"sbt.build.includesbtlaunch",
sys.env.getOrElse("sbt.build.includesbtlaunch", "true")
) match {
) match
case "true" | "1" => true
case "false" | "0" => false
case _ => false
}
lazy val sbtVersionToRelease = sys.props
.getOrElse("sbt.build.version", sys.env.getOrElse("sbt.build.version", "1.12.0"))
lazy val scala210 = "2.10.7"
lazy val scala210Jline = "org.scala-lang" % "jline" % scala210
lazy val jansi = {
if (sbtVersionToRelease.startsWith("1.")) "org.fusesource.jansi" % "jansi" % "1.12"
lazy val jansi =
if sbtVersionToRelease.startsWith("1.") then "org.fusesource.jansi" % "jansi" % "1.12"
else "org.fusesource.jansi" % "jansi" % "1.4"
}
lazy val scala212Compiler = "org.scala-lang" % "scala-compiler" % scala212
lazy val scala212Jline = "jline" % "jline" % "2.14.6"
// use the scala-xml version used by the compiler not the latest: https://github.com/scala/scala/blob/v2.12.21/versions.properties
lazy val scala212Xml = "org.scala-lang.modules" % "scala-xml_2.12" % "2.3.0"
lazy val sbtActual = "org.scala-sbt" % "sbt" % sbtVersionToRelease
lazy val sbt013ExtraDeps = {
if (sbtVersionToRelease.startsWith("0.13.")) Seq(scala210Jline)
lazy val sbt013ExtraDeps =
if sbtVersionToRelease.startsWith("0.13.") then Seq(scala210Jline)
else Seq()
}
lazy val isWindows: Boolean =
sys.props("os.name").toLowerCase(java.util.Locale.ENGLISH).contains("windows")
@@ -115,10 +110,9 @@ val launcherPackage = (project in file("."))
clean.value
},
credentials ++= {
(sys.env.get("BINTRAY_USER"), sys.env.get("BINTRAY_PASS")) match {
(sys.env.get("BINTRAY_USER"), sys.env.get("BINTRAY_PASS")) match
case (Some(u), Some(p)) => Seq(Credentials("Bintray API Realm", "api.bintray.com", u, p))
case _ => Nil
}
},
pgpSecretRing := file(s"""${sys.props("user.home")}""") / ".ssh" / "scalasbt.key",
pgpPublicRing := file(s"""${sys.props("user.home")}""") / ".ssh" / "scalasbt.pub",
@@ -128,20 +122,17 @@ val launcherPackage = (project in file("."))
sbtLaunchJar := {
val uri = sbtLaunchJarUrl.value
val file = sbtLaunchJarLocation.value
if (!file.exists) {
if !file.exists then
// oddly, some places require us to create the file before writing...
IO.touch(file)
val url = new URI(uri).toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
}
// TODO - GPG Trust validation.
fileConverter.value.toVirtualFile(file.toPath)
},
@@ -158,76 +149,64 @@ val launcherPackage = (project in file("."))
val linuxX86Tar = t / linuxX86ImageTar
val linuxAarch64Tar = t / linuxAarch64ImageTar
val windowsZip = t / windowsImageZip
if (!macosUniversalTar.exists && !isWindows && sbtIncludeSbtn) {
if !macosUniversalTar.exists && !isWindows && sbtIncludeSbtn then
IO.touch(macosUniversalTar)
val url = new URI(s"$baseUrl/v$v/$macosUniversalImageTar").toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer =
new java.io.BufferedOutputStream(new java.io.FileOutputStream(macosUniversalTar))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
val platformDir = t / universalMacPlatform
IO.createDirectory(platformDir)
s"tar zxvf $macosUniversalTar --directory $platformDir".!
IO.move(platformDir / "sbtn", t / universalMacImageName)
}
if (!linuxX86Tar.exists && !isWindows && sbtIncludeSbtn) {
if !linuxX86Tar.exists && !isWindows && sbtIncludeSbtn then
IO.touch(linuxX86Tar)
val url = new URI(s"$baseUrl/v$v/$linuxX86ImageTar").toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(linuxX86Tar))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
val platformDir = t / x86LinuxPlatform
IO.createDirectory(platformDir)
s"""tar zxvf $linuxX86Tar --directory $platformDir""".!
IO.move(platformDir / "sbtn", t / x86LinuxImageName)
}
if (!linuxAarch64Tar.exists && !isWindows && sbtIncludeSbtn) {
if !linuxAarch64Tar.exists && !isWindows && sbtIncludeSbtn then
IO.touch(linuxAarch64Tar)
val url = new URI(s"$baseUrl/v$v/$linuxAarch64ImageTar").toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(linuxAarch64Tar))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
val platformDir = t / aarch64LinuxPlatform
IO.createDirectory(platformDir)
s"""tar zxvf $linuxAarch64Tar --directory $platformDir""".!
IO.move(platformDir / "sbtn", t / aarch64LinuxImageName)
}
if (!windowsZip.exists && sbtIncludeSbtn) {
if !windowsZip.exists && sbtIncludeSbtn then
IO.touch(windowsZip)
val url = new URI(s"$baseUrl/v$v/$windowsImageZip").toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(windowsZip))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
val platformDir = t / x86WindowsPlatform
IO.unzip(windowsZip, platformDir)
IO.move(platformDir / "sbtn.exe", t / x86WindowsImageName)
}
if (!sbtIncludeSbtn) Seq()
else if (isWindows)
if !sbtIncludeSbtn then Seq()
else if isWindows then
Seq(
fileConverter.value
.toVirtualFile((t / x86WindowsImageName).toPath) -> s"bin/$x86WindowsImageName"
@@ -251,10 +230,10 @@ val launcherPackage = (project in file("."))
// Here we remove the jar file and launch lib from the symlinks:
linuxPackageSymlinks := {
val links = linuxPackageSymlinks.value
for {
for
link <- links
if !link.destination.endsWith("sbt-launch.jar")
} yield link
yield link
},
// DEBIAN SPECIFIC
@@ -262,7 +241,7 @@ val launcherPackage = (project in file("."))
.getOrElse("sbt.build.patch", sys.env.getOrElse("DIST_PATCHVER", "0"))
.toInt,
Debian / version := {
if (debianBuildId.value == 0) sbtVersionToRelease
if debianBuildId.value == 0 then sbtVersionToRelease
else sbtVersionToRelease + "." + debianBuildId.value
},
// Used to have "openjdk-8-jdk" but that doesn't work on Ubuntu 14.04 https://github.com/sbt/sbt/issues/3105
@@ -287,11 +266,12 @@ val launcherPackage = (project in file("."))
Rpm / version := {
val stable0 = (sbtVersionToRelease.split("[^\\d]") filterNot (_.isEmpty) mkString ".")
val stable =
if (rpmRelease.value == "0") stable0
if rpmRelease.value == "0" then stable0
else stable0 + "." + rpmRelease.value
if (isExperimental) (sbtVersionToRelease.split("[^\\d]").filterNot(_.isEmpty).toList match {
case List(_, _, c, d) => List(0, 99, c, d).mkString(".")
})
if isExperimental then
(sbtVersionToRelease.split("[^\\d]").filterNot(_.isEmpty).toList match
case List(_, _, c, d) => List(0, 99, c, d).mkString(".")
)
else stable
},
// remove sbtn from RPM because it complains about it being noarch
@@ -319,12 +299,11 @@ val launcherPackage = (project in file("."))
Windows / version := {
val bid = windowsBuildId.value
val sv = sbtVersionToRelease
sv.split("[^\\d]").filterNot(_.isEmpty) match {
sv.split("[^\\d]").filterNot(_.isEmpty) match
case Array(major, minor, bugfix, _*) if bid == 0 => Seq(major, minor, bugfix) mkString "."
case Array(major, minor, bugfix, _*) => Seq(major, minor, bugfix, bid.toString) mkString "."
case Array(major, minor) => Seq(major, minor, "0", bid.toString) mkString "."
case Array(major) => Seq(major, "0", "0", bid.toString) mkString "."
}
},
Windows / maintainer := "Scala Center",
Windows / packageSummary := "sbt " + (Windows / version).value,
@@ -360,10 +339,9 @@ val launcherPackage = (project in file("."))
)
)
if (FileSystems.getDefault.supportedFileAttributeViews.contains("posix")) {
if FileSystems.getDefault.supportedFileAttributeViews.contains("posix") then
val perms = Files.getPosixFilePermissions(fileConverter.value.toPath(k))
Files.setPosixFilePermissions((t / "sbt").toPath, perms)
}
(fileConverter.value.toVirtualFile((t / "sbt").toPath), BinSbt)
case (k, BinBat) =>
@@ -380,7 +358,7 @@ val launcherPackage = (project in file("."))
}
},
Universal / mappings ++= (Def.taskDyn {
if (sbtIncludeSbtLaunch)
if sbtIncludeSbtLaunch then
Def.task {
Seq(
sbtLaunchJar.value -> "bin/sbt-launch.jar"
@@ -390,14 +368,14 @@ val launcherPackage = (project in file("."))
}).value,
Universal / mappings ++= sbtnJarsMappings.value,
Universal / mappings ++= (Def.taskDyn {
if (sbtOfflineInstall && sbtVersionToRelease.startsWith("1."))
if sbtOfflineInstall && sbtVersionToRelease.startsWith("1.") then
Def.task {
val _ = ((dist / exportRepoUsingCoursier)).value
directory(((dist / target)).value / "lib").map { (k, v) =>
fileConverter.value.toVirtualFile(k.toPath) -> v
}
}
else if (sbtOfflineInstall)
else if sbtOfflineInstall then
Def.task {
val _ = ((dist / exportRepo)).value
directory(((dist / target)).value / "lib").map { (k, v) =>
@@ -409,7 +387,7 @@ val launcherPackage = (project in file("."))
Universal / mappings ++= {
val base = baseDirectory.value
val converter = fileConverter.value
if (sbtVersionToRelease.startsWith("0.13.")) Nil
if sbtVersionToRelease.startsWith("0.13.") then Nil
else
Seq[(HashedVirtualFileRef, String)](
converter.toVirtualFile((base.getParentFile / "LICENSE").toPath) -> "LICENSE",
@@ -437,7 +415,7 @@ val launcherPackage = (project in file("."))
)
def downloadUrlForVersion(v: String) =
(v.split("[^\\d]") flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match {
(v.split("[^\\d]") flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match
case Array(0, 11, 3, _*) =>
"https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.11.3-2/sbt-launch.jar"
case Array(0, 11, x, _*) if x >= 3 =>
@@ -448,9 +426,8 @@ def downloadUrlForVersion(v: String) =
"https://repo.scala-sbt.org/scalasbt/maven-snapshots/org/scala-sbt/sbt-launch/" + v + "/sbt-launch.jar"
case _ =>
"https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/" + v + "/sbt-launch-" + v + ".jar"
}
def makePublishToForConfig(config: Configuration) = {
def makePublishToForConfig(config: Configuration) =
// Add the publish to and ensure global resolvers has the resolver we just configured.
inConfig(config)(
Seq(
@@ -461,14 +438,13 @@ def makePublishToForConfig(config: Configuration) = {
bintrayRpmExperimentalUrl := s"https://scala.jfrog.io/artifactory/rpm-experimental/",
bintrayGenericPackagesUrl := s"https://scala.jfrog.io/artifactory/native-packages/",
bintrayTripple := {
config.name match {
config.name match
case Debian.name if isExperimental =>
("debian-experimental", bintrayDebianExperimentalUrl.value, artifactoryDebianPattern)
case Debian.name => ("debian", bintrayDebianUrl.value, artifactoryDebianPattern)
case Rpm.name if isExperimental =>
("rpm-experimental", bintrayRpmExperimentalUrl.value, artifactoryLinuxPattern)
case Rpm.name => ("rpm", bintrayRpmUrl.value, artifactoryLinuxPattern)
}
},
publishTo := {
val (id, url, pattern) = bintrayTripple.value
@@ -477,26 +453,21 @@ def makePublishToForConfig(config: Configuration) = {
}
)
)
}
def publishToSettings =
Seq[Configuration](Debian, Rpm) flatMap makePublishToForConfig
def downloadUrl(uri: URI, out: File): Unit = {
if (!out.exists) {
def downloadUrl(uri: URI, out: File): Unit =
if !out.exists then
IO.touch(out)
val url = new URI(uri.toString).toURL
val connection = url.openConnection()
val input = connection.getInputStream
val writer = new java.io.BufferedOutputStream(new java.io.FileOutputStream(out))
try {
input.transferTo(writer)
} finally {
try input.transferTo(writer)
finally
input.close()
writer.close()
}
}
}
def colonName(m: ModuleID): String = s"${m.organization}:${m.name}:${m.revision}"
@@ -504,7 +475,7 @@ lazy val dist = (project in file("dist"))
.settings(
name := "dist",
scalaVersion := {
if (sbtVersionToRelease.startsWith("0.13.")) scala210
if sbtVersionToRelease.startsWith("0.13.") then scala210
else scala212
},
libraryDependencies ++= Seq(
@@ -516,7 +487,7 @@ lazy val dist = (project in file("dist"))
) ++ sbt013ExtraDeps,
exportRepo := {
val outDir = exportRepoDirectory.value
sbtVersionToRelease match {
sbtVersionToRelease match
case v if v.startsWith("1.") =>
sys.error("sbt 1.x should use coursier")
case v if v.startsWith("0.13.") =>
@@ -533,7 +504,6 @@ lazy val dist = (project in file("dist"))
outbase / "srcs" / "compiler-interface-sources.jar"
)
case _ =>
}
outDir
},
exportRepoDirectory := target.value / "lib" / "local-preloaded",
@@ -541,7 +511,7 @@ lazy val dist = (project in file("dist"))
exportRepoUsingCoursier := {
val outDirectory = exportRepoCsrDirectory.value
val csr =
if (isWindows) (LocalRootProject / baseDirectory).value / "bin" / "coursier.bat"
if isWindows then (LocalRootProject / baseDirectory).value / "bin" / "coursier.bat"
else (LocalRootProject / baseDirectory).value / "bin" / "coursier"
val cache = target.value / "coursier"
IO.delete(cache)
@@ -555,7 +525,7 @@ lazy val dist = (project in file("dist"))
.listFiles(mavenCache / "org" / "scala-sbt" / "compiler-bridge_2.12", DirectoryFilter)
.toList
.headOption
compilerBridgeVer match {
compilerBridgeVer match
case Some(bridgeDir) =>
val bridgeVer = bridgeDir.getName
s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.10:$bridgeVer".!
@@ -564,7 +534,6 @@ lazy val dist = (project in file("dist"))
s"$csr fetch --cache $cache --sources org.scala-sbt:compiler-bridge_2.13:$bridgeVer".!
case _ =>
sys.error("bridge not found")
}
IO.copyDirectory(mavenCache, outDirectory, true, true)
outDirectory
},
@@ -61,7 +61,7 @@ object BspConfigTest extends BasicTestSuite:
// Test execution of the generated argv
// Run the BSP command with a very short timeout to verify it starts correctly
// We just need to verify the command doesn't fail immediately on startup
if (!isWindows) {
if !isWindows then
// On Unix, we can test the argv execution
// Create a process and check if it starts (will timeout waiting for BSP input)
val process = Process(argv.toSeq, tmp)
@@ -76,14 +76,13 @@ object BspConfigTest extends BasicTestSuite:
// The process should either still be alive (waiting for BSP messages)
// or have exited with code 0 (graceful)
if (!isAlive) {
if !isAlive then
val exitCode = processBuilder.exitValue()
assert(
exitCode == 0 || exitCode == 143, // 143 = SIGTERM from destroy()
s"BSP process failed with exit code $exitCode"
)
}
}
end if
}
()
}

Some files were not shown because too many files have changed in this diff Show More