mirror of
https://github.com/lightbend/config.git
synced 2025-01-15 23:01:05 +08:00
Merge remote-tracking branch 'origin/master' into wip/havocp-resolve-fixes
Conflicts: config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java
This commit is contained in:
commit
1afaea55a8
15
HOCON.md
15
HOCON.md
@ -863,7 +863,7 @@ usual the comma may be omitted if there's a newline).
|
||||
|
||||
If an unquoted `include` at the start of a key is followed by
|
||||
anything other than a single quoted string or the
|
||||
`url("")`/`file("")/`classpath("")` syntax, it is invalid and an
|
||||
`url("")`/`file("")`/`classpath("")` syntax, it is invalid and an
|
||||
error should be generated.
|
||||
|
||||
There can be any amount of whitespace, including newlines, between
|
||||
@ -1209,9 +1209,9 @@ parsed as a number plus an optional unit string.
|
||||
The supported unit strings for duration are case sensitive and
|
||||
must be lowercase. Exactly these strings are supported:
|
||||
|
||||
- `ns`, `nanosecond`, `nanoseconds`
|
||||
- `us`, `microsecond`, `microseconds`
|
||||
- `ms`, `millisecond`, `milliseconds`
|
||||
- `ns`, `nano`, `nanos`, `nanosecond`, `nanoseconds`
|
||||
- `us`, `micro`, `micros`, `microsecond`, `microseconds`
|
||||
- `ms`, `milli`, `millis`, `millisecond`, `milliseconds`
|
||||
- `s`, `second`, `seconds`
|
||||
- `m`, `minute`, `minutes`
|
||||
- `h`, `hour`, `hours`
|
||||
@ -1274,6 +1274,13 @@ spec copies that. You can certainly find examples of mapping these
|
||||
to powers of ten, though. If you don't like ambiguity, don't use
|
||||
the single-letter abbreviations.
|
||||
|
||||
Note: any value in zetta/zebi or yotta/yobi will overflow a 64-bit
|
||||
integer, and of course large-enough values in any of the units may
|
||||
overflow. Most real-world APIs and apps will not support byte
|
||||
counts that overflow a 64-bit integer. The huge units are provided
|
||||
just to be complete but probably aren't useful in practice. At
|
||||
least not in 2014.
|
||||
|
||||
### Config object merging and file merging
|
||||
|
||||
It may be useful to offer a method to merge two objects. If such a
|
||||
|
@ -92,6 +92,10 @@ Maven Central.
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
|
||||
sbt dependency:
|
||||
|
||||
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
|
||||
|
||||
Link for direct download if you don't use a dependency manager:
|
||||
|
||||
- http://central.maven.org/maven2/com/typesafe/config/
|
||||
@ -743,3 +747,7 @@ format.
|
||||
#### Ruby port
|
||||
|
||||
* https://github.com/cprice404/ruby-hocon
|
||||
|
||||
#### Python port
|
||||
|
||||
* pyhocon https://github.com/chimpler/pyhocon
|
||||
|
@ -16,7 +16,7 @@ crossPaths := false
|
||||
|
||||
libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test"
|
||||
|
||||
libraryDependencies += "com.novocode" % "junit-interface" % "0.10-M4" % "test"
|
||||
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
|
||||
|
||||
externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/"
|
||||
|
||||
|
@ -591,10 +591,35 @@ public final class ConfigFactory {
|
||||
return parseURL(url, ConfigParseOptions.defaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a file into a Config instance. Does not call
|
||||
* {@link Config#resolve} or merge the file with any other
|
||||
* configuration; this method parses a single file and does
|
||||
* nothing else. It does process "include" statements in the
|
||||
* parsed file, and may end up doing other IO due to those
|
||||
* statements.
|
||||
*
|
||||
* @param file
|
||||
* the file to parse
|
||||
* @param options
|
||||
* parse options to control how the file is interpreted
|
||||
* @return the parsed configuration
|
||||
* @throws ConfigException on IO or parse errors
|
||||
*/
|
||||
public static Config parseFile(File file, ConfigParseOptions options) {
|
||||
return Parseable.newFile(file, options).parse().toConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a file into a Config instance as with
|
||||
* {@link #parseFile(File,ConfigParseOptions)} but always uses the
|
||||
* default parse options.
|
||||
*
|
||||
* @param file
|
||||
* the file to parse
|
||||
* @return the parsed configuration
|
||||
* @throws ConfigException on IO or parse errors
|
||||
*/
|
||||
public static Config parseFile(File file) {
|
||||
return parseFile(file, ConfigParseOptions.defaults());
|
||||
}
|
||||
@ -637,6 +662,14 @@ public final class ConfigFactory {
|
||||
return ConfigImpl.parseFileAnySyntax(fileBasename, options).toConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #parseFileAnySyntax(File,ConfigParseOptions)} but always uses
|
||||
* default parse options.
|
||||
*
|
||||
* @param fileBasename
|
||||
* a filename with or without extension
|
||||
* @return the parsed configuration
|
||||
*/
|
||||
public static Config parseFileAnySyntax(File fileBasename) {
|
||||
return parseFileAnySyntax(fileBasename, ConfigParseOptions.defaults());
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ package com.typesafe.config.impl;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -512,12 +514,12 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
unitString = unitString + "s";
|
||||
|
||||
// note that this is deliberately case-sensitive
|
||||
if (unitString.equals("") || unitString.equals("ms")
|
||||
if (unitString.equals("") || unitString.equals("ms") || unitString.equals("millis")
|
||||
|| unitString.equals("milliseconds")) {
|
||||
units = TimeUnit.MILLISECONDS;
|
||||
} else if (unitString.equals("us") || unitString.equals("microseconds")) {
|
||||
} else if (unitString.equals("us") || unitString.equals("micros") || unitString.equals("microseconds")) {
|
||||
units = TimeUnit.MICROSECONDS;
|
||||
} else if (unitString.equals("ns") || unitString.equals("nanoseconds")) {
|
||||
} else if (unitString.equals("ns") || unitString.equals("nanos") || unitString.equals("nanoseconds")) {
|
||||
units = TimeUnit.NANOSECONDS;
|
||||
} else if (unitString.equals("d") || unitString.equals("days")) {
|
||||
units = TimeUnit.DAYS;
|
||||
@ -575,19 +577,13 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
final String prefix;
|
||||
final int powerOf;
|
||||
final int power;
|
||||
final long bytes;
|
||||
final BigInteger bytes;
|
||||
|
||||
MemoryUnit(String prefix, int powerOf, int power) {
|
||||
this.prefix = prefix;
|
||||
this.powerOf = powerOf;
|
||||
this.power = power;
|
||||
int i = power;
|
||||
long bytes = 1;
|
||||
while (i > 0) {
|
||||
bytes *= powerOf;
|
||||
--i;
|
||||
}
|
||||
this.bytes = bytes;
|
||||
this.bytes = BigInteger.valueOf(powerOf).pow(power);
|
||||
}
|
||||
|
||||
private static Map<String, MemoryUnit> makeUnitsMap() {
|
||||
@ -667,13 +663,20 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
}
|
||||
|
||||
try {
|
||||
BigInteger result;
|
||||
// if the string is purely digits, parse as an integer to avoid
|
||||
// possible precision loss; otherwise as a double.
|
||||
if (numberString.matches("[0-9]+")) {
|
||||
return Long.parseLong(numberString) * units.bytes;
|
||||
result = units.bytes.multiply(new BigInteger(numberString));
|
||||
} else {
|
||||
return (long) (Double.parseDouble(numberString) * units.bytes);
|
||||
BigDecimal resultDecimal = (new BigDecimal(units.bytes)).multiply(new BigDecimal(numberString));
|
||||
result = resultDecimal.toBigInteger();
|
||||
}
|
||||
if (result.bitLength() < 64)
|
||||
return result.longValue();
|
||||
else
|
||||
throw new ConfigException.BadValue(originForException, pathForException,
|
||||
"size-in-bytes value is out of range for a 64-bit long: '" + input + "'");
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException, pathForException,
|
||||
"Could not parse size-in-bytes number '" + numberString + "'");
|
||||
|
@ -115,7 +115,11 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
|
||||
}
|
||||
|
||||
if (changed != null) {
|
||||
if (newResolveStatus != null) {
|
||||
return new SimpleConfigList(origin(), changed, newResolveStatus);
|
||||
} else {
|
||||
return new SimpleConfigList(origin(), changed);
|
||||
}
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
@ -151,7 +155,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
|
||||
} else {
|
||||
try {
|
||||
ResolveModifier modifier = new ResolveModifier(context, source.pushParent(this));
|
||||
SimpleConfigList value = modifyMayThrow(modifier, ResolveStatus.RESOLVED);
|
||||
SimpleConfigList value = modifyMayThrow(modifier, context.options().getAllowUnresolved() ? null : ResolveStatus.RESOLVED);
|
||||
return ResolveResult.make(modifier.context, value);
|
||||
} catch (NotPossibleToResolve e) {
|
||||
throw e;
|
||||
|
@ -457,23 +457,22 @@ final class Tokenizer {
|
||||
private Token pullQuotedString() throws ProblemException {
|
||||
// the open quote has already been consumed
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int c = '\0'; // value doesn't get used
|
||||
do {
|
||||
c = nextCharRaw();
|
||||
while (true) {
|
||||
int c = nextCharRaw();
|
||||
if (c == -1)
|
||||
throw problem("End of input but string quote was still open");
|
||||
|
||||
if (c == '\\') {
|
||||
pullEscapeSequence(sb);
|
||||
} else if (c == '"') {
|
||||
// end the loop, done!
|
||||
break;
|
||||
} else if (Character.isISOControl(c)) {
|
||||
throw problem(asString(c), "JSON does not allow unescaped " + asString(c)
|
||||
+ " in quoted strings, use a backslash escape");
|
||||
} else {
|
||||
sb.appendCodePoint(c);
|
||||
}
|
||||
} while (c != '"');
|
||||
}
|
||||
|
||||
// maybe switch to triple-quoted string, sort of hacky...
|
||||
if (sb.length() == 0) {
|
||||
|
@ -54,7 +54,9 @@
|
||||
"second" : 1s,
|
||||
"secondsList" : [1s,2seconds,3 s, 4000],
|
||||
"secondAsNumber" : 1000,
|
||||
"halfSecond" : 0.5s
|
||||
"halfSecond" : 0.5s,
|
||||
"millis" : 1 milli,
|
||||
"micros" : 2000 micros
|
||||
},
|
||||
|
||||
"memsizes" : {
|
||||
|
@ -769,6 +769,8 @@ class ConfigTest extends TestUtils {
|
||||
assertEquals(Seq(1, 2, 3, 4) map s2unit,
|
||||
conf.getDurationList("durations.secondsList", unit).asScala)
|
||||
assertEquals(ms2unit(500L), conf.getDuration("durations.halfSecond", unit))
|
||||
assertEquals(ms2unit(1L), conf.getDuration("durations.millis", unit))
|
||||
assertEquals(ms2unit(2L), conf.getDuration("durations.micros", unit))
|
||||
}
|
||||
|
||||
assertDurationAsTimeUnit(NANOSECONDS)
|
||||
@ -1118,6 +1120,15 @@ class ConfigTest extends TestUtils {
|
||||
assertTrue("after resolution, config is now resolved", resolved2.isResolved)
|
||||
}
|
||||
|
||||
@Test
|
||||
def allowUnresolvedDoesAllowUnresolvedArrayElements() {
|
||||
val values = ConfigFactory.parseString("unknown = [someVal], known = 42")
|
||||
val unresolved = ConfigFactory.parseString("concat = [${unknown}[]], sibling = [${unknown}, ${known}]")
|
||||
unresolved.resolve(ConfigResolveOptions.defaults().setAllowUnresolved(true))
|
||||
unresolved.withFallback(values).resolve()
|
||||
unresolved.resolveWith(values)
|
||||
}
|
||||
|
||||
@Test
|
||||
def allowUnresolvedDoesAllowUnresolved() {
|
||||
val values = ConfigFactory.parseString("{ foo = 1, bar = 2, m = 3, n = 4}")
|
||||
|
@ -59,7 +59,10 @@ class UnitParserTest extends TestUtils {
|
||||
|
||||
@Test
|
||||
def parseMemorySizeInBytes(): Unit = {
|
||||
def parseMem(s: String) = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
|
||||
def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
|
||||
|
||||
assertEquals(Long.MaxValue, parseMem(s"${Long.MaxValue} bytes"))
|
||||
assertEquals(Long.MinValue, parseMem(s"${Long.MinValue} bytes"))
|
||||
|
||||
val oneMebis = List("1048576", "1048576b", "1048576bytes", "1048576byte",
|
||||
"1048576 b", "1048576 bytes",
|
||||
@ -88,7 +91,7 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
|
||||
var result = 1024L * 1024 * 1024
|
||||
for (unit <- Seq("tebi", "pebi", "exbi", "zebi", "yobi")) {
|
||||
for (unit <- Seq("tebi", "pebi", "exbi")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
result = result * 1024;
|
||||
assertEquals(result, parseMem("1" + first))
|
||||
@ -99,7 +102,7 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
|
||||
result = 1000L * 1000 * 1000
|
||||
for (unit <- Seq("tera", "peta", "exa", "zetta", "yotta")) {
|
||||
for (unit <- Seq("tera", "peta", "exa")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
result = result * 1000;
|
||||
assertEquals(result, parseMem("1" + first + "B"))
|
||||
@ -119,4 +122,45 @@ class UnitParserTest extends TestUtils {
|
||||
}
|
||||
assertTrue(e2.getMessage().contains("size-in-bytes number"))
|
||||
}
|
||||
|
||||
// later on we'll want to check this with BigInteger version of getBytes
|
||||
@Test
|
||||
def parseHugeMemorySizes(): Unit = {
|
||||
def parseMem(s: String): Long = SimpleConfig.parseBytes(s, fakeOrigin(), "test")
|
||||
def assertOutOfRange(s: String) = {
|
||||
val fail = intercept[ConfigException.BadValue] {
|
||||
parseMem(s)
|
||||
}
|
||||
assertTrue("number was too big", fail.getMessage.contains("out of range"))
|
||||
}
|
||||
|
||||
import java.math.BigInteger
|
||||
assertOutOfRange(s"${BigInteger.valueOf(Long.MaxValue).add(BigInteger.valueOf(1)).toString} bytes")
|
||||
assertOutOfRange(s"${BigInteger.valueOf(Long.MinValue).subtract(BigInteger.valueOf(1)).toString} bytes")
|
||||
|
||||
var result = 1024L * 1024 * 1024
|
||||
for (unit <- Seq("zebi", "yobi")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
assertOutOfRange("1" + first)
|
||||
assertOutOfRange("1" + first + "i")
|
||||
assertOutOfRange("1" + first + "iB")
|
||||
assertOutOfRange("1" + unit + "byte")
|
||||
assertOutOfRange("1" + unit + "bytes")
|
||||
assertOutOfRange("1.1" + first)
|
||||
assertOutOfRange("-1" + first)
|
||||
}
|
||||
|
||||
result = 1000L * 1000 * 1000
|
||||
for (unit <- Seq("zetta", "yotta")) {
|
||||
val first = unit.substring(0, 1).toUpperCase()
|
||||
assertOutOfRange("1" + first + "B")
|
||||
assertOutOfRange("1" + unit + "byte")
|
||||
assertOutOfRange("1" + unit + "bytes")
|
||||
assertOutOfRange("1.1" + first + "B")
|
||||
assertOutOfRange("-1" + first + "B")
|
||||
}
|
||||
|
||||
assertOutOfRange("1000 exabytes")
|
||||
assertOutOfRange("10000000 petabytes")
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
sbt.version=0.13.1
|
||||
sbt.version=0.13.7
|
||||
|
Loading…
Reference in New Issue
Block a user