mirror of
https://github.com/sbt/sbt.git
synced 2026-10-06 18:14:04 +02:00
Apply Scalafmt format (Scala 3 syntax)
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
+21
-31
@@ -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
-16
@@ -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
|
||||
|
||||
+4
-9
@@ -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
|
||||
}
|
||||
|
||||
+9
-8
@@ -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
|
||||
|
||||
+36
-61
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-14
@@ -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
|
||||
|
||||
+14
-20
@@ -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:
|
||||
|
||||
+2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -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
|
||||
|
||||
+6
-8
@@ -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
|
||||
}
|
||||
|
||||
+13
-22
@@ -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
@@ -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
Reference in New Issue
Block a user