Add parser for file size

At the suggestion of @eed3si9n, instead of specifying the file cache
size in bytes, we now specify it in a formatted string. For example,
instead of specifying 128 megabytes in bytes (134217728), we can specify
it with the string "128M".
This commit is contained in:
Ethan Atkins 2019-07-09 13:46:59 -07:00
parent cad89d17a9
commit 0172d118af
7 changed files with 137 additions and 4 deletions

View File

@ -219,6 +219,12 @@ trait Parsers {
(DQuoteChar ~> (NotDQuoteBackslashClass | EscapeSequence).+.string <~ DQuoteChar |
(DQuoteChar ~ DQuoteChar) ^^^ "")
/**
* Parses a size unit string. For example, `128K` parsers to `128L * 1024`, and `1.25g` parses
* to `1024L * 1024 * 1024 * 5 / 4`.
*/
lazy val Size: Parser[Long] = SizeParser.value
/**
* Parses a brace enclosed string and, if each opening brace is matched with a closing brace,
* it returns the entire string including the braces.

View File

@ -0,0 +1,56 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.complete
import sbt.internal.util.complete.DefaultParsers._
private[sbt] object SizeParser {
def apply(s: String): Option[Long] = Parser.parse(s, value).toOption
private sealed trait SizeUnit
private case object Bytes extends SizeUnit
private case object KiloBytes extends SizeUnit
private case object MegaBytes extends SizeUnit
private case object GigaBytes extends SizeUnit
private def parseDouble(s: String): Parser[Either[Double, Long]] =
try Parser.success(Left(java.lang.Double.valueOf(s)))
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
private def parseLong(s: String): Parser[Either[Double, Long]] =
try Parser.success(Right(java.lang.Long.valueOf(s)))
catch { case _: NumberFormatException => Parser.failure(s"Couldn't parse $s as double.") }
private[this] val digit = charClass(_.isDigit, "digit")
private[this] val numberParser: Parser[Either[Double, Long]] =
(digit.+ ~ ('.'.examples() ~> digit.+).?).flatMap {
case (leading, Some(decimalPart)) =>
parseDouble(s"${leading.mkString}.${decimalPart.mkString}")
case (leading, _) => parseLong(leading.mkString)
}
private[this] val unitParser: Parser[SizeUnit] =
token("b" | "B" | "g" | "G" | "k" | "K" | "m" | "M").map {
case "b" | "B" => Bytes
case "g" | "G" => GigaBytes
case "k" | "K" => KiloBytes
case "m" | "M" => MegaBytes
}
private[this] 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 {
case (number, unit) =>
unit match {
case None | Some(Bytes) => multiply(number, right = 1)
case Some(KiloBytes) => multiply(number, right = 1024)
case Some(MegaBytes) => multiply(number, right = 1024 * 1024)
case Some(GigaBytes) => multiply(number, right = 1024 * 1024 * 1024)
}
}
}

View File

@ -0,0 +1,63 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util.complete
import org.scalatest.FlatSpec
class SizeParserSpec extends FlatSpec {
"SizeParser" should "handle raw bytes" in {
assert(Parser.parse(str = "123456", SizeParser.value) == Right(123456L))
}
it should "handle bytes" in {
assert(Parser.parse(str = "123456b", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456B", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456 b", SizeParser.value) == Right(123456L))
assert(Parser.parse(str = "123456 B", SizeParser.value) == Right(123456L))
}
it should "handle kilobytes" in {
assert(Parser.parse(str = "123456k", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456K", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
assert(Parser.parse(str = "123456 K", SizeParser.value) == Right(123456L * 1024))
}
it should "handle megabytes" in {
assert(Parser.parse(str = "123456m", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456M", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
assert(Parser.parse(str = "123456 M", SizeParser.value) == Right(123456L * 1024 * 1024))
}
it should "handle gigabytes" in {
assert(Parser.parse(str = "123456g", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
assert(Parser.parse(str = "123456 G", SizeParser.value) == Right(123456L * 1024 * 1024 * 1024))
}
it should "handle doubles" in {
assert(Parser.parse(str = "1.25g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 g", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
assert(Parser.parse(str = "1.25 G", SizeParser.value) == Right(5L * 1024 * 1024 * 1024 / 4))
}
private val expectedCompletions = Set("", "b", "B", "g", "G", "k", "K", "m", "M", " ")
it should "have completions for long" in {
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for long with spaces" in {
val completions = Parser.completions(SizeParser.value, "123", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for double " in {
val completions = Parser.completions(SizeParser.value, "1.25", level = 0).get.map(_.display)
assert(completions == expectedCompletions)
}
it should "have completions for double with spaces" in {
val completions = Parser.completions(SizeParser.value, "1.25 ", level = 0).get.map(_.display)
assert(completions == expectedCompletions + "")
}
}

View File

@ -258,6 +258,7 @@ object Defaults extends BuildCommon {
checkBuildSources / Continuous.dynamicInputs := None,
checkBuildSources / fileInputs := CheckBuildSources.buildSourceFileInputs.value,
checkBuildSources := CheckBuildSources.needReloadImpl.value,
fileCacheSize := "128M",
trapExit :== true,
connectInput :== false,
cancelable :== true,

View File

@ -489,7 +489,7 @@ object Keys {
val globalPluginUpdate = taskKey[UpdateReport]("A hook to get the UpdateReport of the global plugin.").withRank(DTask)
private[sbt] val taskCancelStrategy = settingKey[State => TaskCancellationStrategy]("Experimental task cancellation handler.").withRank(DTask)
private[sbt] val cacheStoreFactory = AttributeKey[CacheStoreFactoryFactory]("cache-store-factory")
val fileCacheSize = settingKey[Long]("The approximate maximum size in bytes of the cache used to store previous task results.")
val fileCacheSize = settingKey[String]("The approximate maximum size in bytes of the cache used to store previous task results. For example, it could be set to \"256M\" to make the maximum size 256 megabytes.")
// Experimental in sbt 0.13.2 to enable grabbing semantic compile failures.
private[sbt] val compilerReporter = taskKey[xsbti.Reporter]("Experimental hook to listen (or send) compilation failure messages.").withRank(DTask)

View File

@ -23,7 +23,7 @@ import sbt.internal._
import sbt.internal.inc.ScalaInstance
import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.util._
import sbt.internal.util.complete.Parser
import sbt.internal.util.complete.{ SizeParser, Parser }
import sbt.io._
import sbt.io.syntax._
import sbt.util.{ Level, Logger, Show }
@ -848,7 +848,11 @@ object BuiltinCommands {
}
private val addCacheStoreFactoryFactory: State => State = (s: State) => {
val size = Project.extract(s).getOpt(Keys.fileCacheSize).getOrElse(SysProp.fileCacheSize)
val size = Project
.extract(s)
.getOpt(Keys.fileCacheSize)
.flatMap(SizeParser(_))
.getOrElse(SysProp.fileCacheSize)
s.get(Keys.cacheStoreFactory).foreach(_.close())
s.put(Keys.cacheStoreFactory, InMemoryCacheStore.factory(size))
}

View File

@ -9,8 +9,10 @@ package sbt
package internal
import java.util.Locale
import scala.util.control.NonFatal
import sbt.internal.util.ConsoleAppender
import sbt.internal.util.complete.SizeParser
// See also BuildPaths.scala
// See also LineReader.scala
@ -84,7 +86,8 @@ object SysProp {
def closeClassLoaders: Boolean = getOrTrue("sbt.classloader.close")
def fileCacheSize: Long = long("sbt.file.cache.size", 128 * 1024 * 1024)
def fileCacheSize: Long =
SizeParser(System.getProperty("sbt.file.cache.size", "128M")).getOrElse(128 * 1024 * 1024)
def supershell: Boolean = color && getOrTrue("sbt.supershell")
def supershellSleep: Long = long("sbt.supershell.sleep", 100L)