Support variable substitution in includes

This commit is contained in:
Ryan O'Neill 2015-08-05 15:54:24 -07:00
parent aac122ae01
commit 730ad27628
24 changed files with 95 additions and 260 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.classpath
.project
.cache
.DS_Store
.settings
.idea
.idea_modules

View File

@ -1,59 +0,0 @@
# Submitting pull requests
Pull requests should go via GitHub; there are some nice
[general guidelines for contributing on GitHub](https://guides.github.com/activities/contributing-to-open-source/)
if you haven't done it before.
Unless your fix is trivial, it's a good idea to look for related
discussions and maybe open an issue to discuss, before making a
pull request. Discussion in advance may keep you from wasting time
on an approach that won't work out. However, if you communicate
better in code than in words, starting with a patch is
fine... just be willing to revise it!
Before we can accept pull requests, you will need to agree to the
Typesafe Contributor License Agreement online, using your GitHub
account - it takes 30 seconds. You can do this at
http://www.typesafe.com/contribute/cla
Expect that most PRs will need revision before merge. If people
suggest revisions, you can make them yourself or wait for a
maintainer to do it on their own timeline. The larger the PR, the
more revision will likely be needed.
# Making a release
To make a release you'll need to be a maintainer with GitHub
permissions to push to the master and gh-pages branches, and
Sonatype permissions to publish.
Here are the steps, which should be automated but aren't (PR
welcome!):
1. write release notes in NEWS.md following the format
already in there. commit.
2. create a signed git tag "vX.Y.Z"
3. start sbt; `show version` should confirm that the version was
taken from the tag
4. clean
5. test (double check that release works)
6. doc (double check that docs build, plus build docs
to be copied to gh-pages later)
7. if test or doc fails, delete the tag, fix it, start over.
8. publishSigned
9. make a separate clone of the repo in another directory and
check out the gh-pages branch
10. /bin/rm -rf latest/api on gh-pages checkout
11. copy config/target/api from master checkout to vX.Y.Z in
gh-pages so you have vX.Y.Z/index.html
12. copy config/target/api from master checkout into latest/
so you have latest/api/index.html
13. commit all that to gh-pages, check the diff for sanity
(latest/api should be mostly just changed timestamps)
14. push gh-pages
15. log into sonatype website and go through the usual hoops
(search for com.typesafe, verify the artifacts in it, close,
release)
16. push the "vX.Y.Z" tag
17. announce release, possibly wait for maven central to sync
first

View File

@ -1025,6 +1025,19 @@ Then the `${x}` in "foo.conf", which has been fixed up to
`${a.x}`, would evaluate to `42` rather than to `10`.
Substitution happens _after_ parsing the whole configuration.
Includes inside of lists use the list index as a path segment when
fixing up substitutions.
Example:
{
a : [ { include "foo.conf" } ]
a.0.x : 42
}
Since the `${x}` in "foo.conf" would be fixed up to `${a.0.x}' we
get our expected value of 42.
However, there are plenty of cases where the included file might
intend to refer to the application's root config. For example, to
get a value from a system property or from the reference

10
NEWS.md
View File

@ -1,13 +1,3 @@
# 1.3.0: May 8, 2015
- no changes from 1.3.0-M3
- this is an ABI-guaranteed stable release.
- 1.3.0 should be ABI-compatible with 1.2.x for most applications,
though there are enough changes to corner cases and
implementation that some obscure things could break. Please see
the notes for the 1.3.0 milestones below for details, especially
for 1.3.0-M1.
# 1.3.0-M3: April 21, 2015
- this is an ABI-not-guaranteed beta release in advance

View File

@ -93,19 +93,19 @@ The license is Apache 2.0, see LICENSE-2.0.txt.
### Binary Releases
Version 1.2.1 and earlier were built for Java 6, while newer
versions (1.3.0 and above) will be built for Java 8.
versions (1.3.0-M1 is the current beta) will be built for Java 8.
You can find published releases on Maven Central.
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.0</version>
<version>1.2.1</version>
</dependency>
sbt dependency:
libraryDependencies += "com.typesafe" % "config" % "1.3.0"
libraryDependencies += "com.typesafe" % "config" % "1.2.1"
Link for direct download if you don't use a dependency manager:
@ -135,10 +135,6 @@ Typesafe Contributor License Agreement online, using your GitHub
account - it takes 30 seconds. You can do this at
http://www.typesafe.com/contribute/cla
Please see
[CONTRIBUTING](https://github.com/typesafehub/config/blob/master/CONTRIBUTING.md)
for more including how to make a release.
### Build
The build uses sbt and the tests are written in Scala; however,
@ -752,14 +748,6 @@ If you have trouble with your configuration, some useful tips.
Currently the library is maintained against Java 8, but
version 1.2.1 and earlier will work with Java 6.
Please use 1.2.1 if you need Java 6 support, though some people
have expressed interest in a branch off of 1.3.0 supporting
Java 7. If you want to work on that branch you might bring it up
on [chat](https://gitter.im/typesafehub/config). We can release a
jar for Java 7 if someone(s) steps up to maintain the branch. The
master branch does not use Java 8 "gratuitously" but some APIs
that use Java 8 types will need to be removed.
### Rationale for Supported File Formats
(For the curious.)
@ -812,9 +800,6 @@ your wrapper, just send a pull request for this README. We would
love to know what you're doing with this library or with the HOCON
format.
#### Guice integration
* Typesafe Config Guice https://github.com/racc/typesafeconfig-guice
#### Scala wrappers for the Java library
* Ficus https://github.com/ceedubs/ficus

View File

@ -193,13 +193,6 @@ public class ConfigBeanImpl {
return config.getObjectList(configPropName);
} else if (elementType == ConfigValue.class) {
return config.getList(configPropName);
} else if (hasAtLeastOneBeanProperty((Class<?>) elementType)) {
List<Object> beanList = new ArrayList<Object>();
List<? extends Config> configList = config.getConfigList(configPropName);
for (Config listMember : configList) {
beanList.add(createInternal(listMember, (Class<?>) elementType));
}
return beanList;
} else {
throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType);
}

View File

@ -270,11 +270,6 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl
render(stack, sb, indent, atRoot, atKey, options);
}
@Override
protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) {
render(sb, indent, atRoot, null, options);
}
// static method also used by ConfigDelayedMergeObject.
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, boolean atRoot, String atKey,
ConfigRenderOptions options) {

View File

@ -32,10 +32,6 @@ final public class ConfigImplUtil {
return a.equals(b);
}
static boolean isC0Control(int codepoint) {
return (codepoint >= 0x0000 && codepoint <= 0x001F);
}
public static String renderJsonString(String s) {
StringBuilder sb = new StringBuilder();
sb.append('"');
@ -64,7 +60,7 @@ final public class ConfigImplUtil {
sb.append("\\t");
break;
default:
if (isC0Control(c))
if (Character.isISOControl(c))
sb.append(String.format("\\u%04x", (int) c));
else
sb.append(c);

View File

@ -121,9 +121,6 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
private Collection<AbstractConfigNode> indentation() {
boolean seenNewLine = false;
ArrayList<AbstractConfigNode> indentation = new ArrayList<AbstractConfigNode>();
if (children.isEmpty()) {
return indentation;
}
for (int i = 0; i < children.size(); i++) {
if (!seenNewLine) {
if (children.get(i) instanceof ConfigNodeSingleToken &&
@ -144,18 +141,19 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
}
if (indentation.isEmpty()) {
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));
return indentation;
} else {
// Calculate the indentation of the ending curly-brace to get the indentation of the root object
AbstractConfigNode last = children.get(children.size() - 1);
if (last instanceof ConfigNodeSingleToken && ((ConfigNodeSingleToken) last).token() == Tokens.CLOSE_CURLY) {
AbstractConfigNode beforeLast = children.get(children.size() - 2);
String indent = "";
if (beforeLast instanceof ConfigNodeSingleToken &&
Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token()))
indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText();
indent += " ";
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent)));
return indentation;
Tokens.isIgnoredWhitespace(((ConfigNodeSingleToken) beforeLast).token())) {
String indent = ((ConfigNodeSingleToken) beforeLast).token().tokenText();
indent += " ";
indentation.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, indent)));
return indentation;
}
}
}
@ -170,12 +168,12 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
// If the value we're inserting is a complex value, we'll need to indent it for insertion
AbstractConfigNodeValue indentedValue;
if (value instanceof ConfigNodeComplexValue && !indentation.isEmpty()) {
if (value instanceof ConfigNodeComplexValue) {
indentedValue = ((ConfigNodeComplexValue) value).indentText(indentation.get(indentation.size() - 1));
} else {
indentedValue = value;
}
boolean sameLine = !(indentation.size() > 0 && indentation.get(0) instanceof ConfigNodeSingleToken &&
boolean sameLine = !(indentation.get(0) instanceof ConfigNodeSingleToken &&
Tokens.isNewline(((ConfigNodeSingleToken) indentation.get(0)).token()));
// If the path is of length greater than one, see if the value needs to be added further down
@ -211,10 +209,7 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
// If the path is of length greater than one add the required new objects along the path
ArrayList<AbstractConfigNode> newObjectNodes = new ArrayList<AbstractConfigNode>();
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.OPEN_CURLY));
if (indentation.isEmpty()) {
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newLine(null)));
}
newObjectNodes.addAll(indentation);
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.newIgnoredWhitespace(null, " ")));
newObjectNodes.add(new ConfigNodeSingleToken(Tokens.CLOSE_CURLY));
ConfigNodeObject newObject = new ConfigNodeObject(newObjectNodes);
newNodes.add(newObject.addValueOnPath(desiredPath.subPath(1), indentedValue, flavor));

View File

@ -34,11 +34,6 @@ final class ConfigParser {
final private ConfigOrigin baseOrigin;
final private LinkedList<Path> pathStack;
// the number of lists we are inside; this is used to detect the "cannot
// generate a reference to a list element" problem, and once we fix that
// problem we should be able to get rid of this variable.
int arrayCount;
ParseContext(ConfigSyntax flavor, ConfigOrigin origin, ConfigNodeRoot document,
FullIncluder includer, ConfigIncludeContext includeContext) {
lineNumber = 1;
@ -48,7 +43,6 @@ final class ConfigParser {
this.includer = includer;
this.includeContext = includeContext;
this.pathStack = new LinkedList<Path>();
this.arrayCount = 0;
}
// merge a bunch of adjacent values into one
@ -95,8 +89,6 @@ final class ConfigParser {
private AbstractConfigValue parseValue(AbstractConfigNodeValue n, List<String> comments) {
AbstractConfigValue v;
int startingArrayCount = arrayCount;
if (n instanceof ConfigNodeSimpleValue) {
v = ((ConfigNodeSimpleValue) n).value();
} else if (n instanceof ConfigNodeObject) {
@ -114,9 +106,6 @@ final class ConfigParser {
comments.clear();
}
if (arrayCount != startingArrayCount)
throw new ConfigException.BugOrBroken("Bug in config parser: unbalanced array count");
return v;
}
@ -187,14 +176,6 @@ final class ConfigParser {
throw new ConfigException.BugOrBroken("should not be reached");
}
// we really should make this work, but for now throwing an
// exception is better than producing an incorrect result.
// See https://github.com/typesafehub/config/issues/160
if (arrayCount > 0 && obj.resolveStatus() != ResolveStatus.RESOLVED)
throw parseError("Due to current limitations of the config parser, when an include statement is nested inside a list value, "
+ "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or "
+ "remove the ${} statements from the included file.");
if (!pathStack.isEmpty()) {
Path prefix = fullCurrentPath();
obj = obj.relativized(prefix);
@ -240,21 +221,6 @@ final class ConfigParser {
// path must be on-stack while we parse the value
pathStack.push(path);
if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) {
// we really should make this work, but for now throwing
// an exception is better than producing an incorrect
// result. See
// https://github.com/typesafehub/config/issues/160
if (arrayCount > 0)
throw parseError("Due to current limitations of the config parser, += does not work nested inside a list. "
+ "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. "
+ "You might be able to move the += outside of the list and then refer to it from inside the list with ${}.");
// because we will put it in an array after the fact so
// we want this to be incremented during the parseValue
// below in order to throw the above exception.
arrayCount += 1;
}
AbstractConfigNodeValue valueNode;
AbstractConfigValue newValue;
@ -265,7 +231,6 @@ final class ConfigParser {
newValue = parseValue(valueNode, comments);
if (((ConfigNodeField) node).separator() == Tokens.PLUS_EQUALS) {
arrayCount -= 1;
List<AbstractConfigValue> concat = new ArrayList<AbstractConfigValue>(2);
AbstractConfigValue previousRef = new ConfigReference(newValue.origin(),
@ -346,7 +311,6 @@ final class ConfigParser {
}
private SimpleConfigList parseArray(ConfigNodeArray n) {
arrayCount += 1;
SimpleConfigOrigin arrayOrigin = lineOrigin();
List<AbstractConfigValue> values = new ArrayList<AbstractConfigValue>();
@ -356,6 +320,8 @@ final class ConfigParser {
AbstractConfigValue v = null;
int index = 0;
for (AbstractConfigNode node : n.children()) {
if (node instanceof ConfigNodeComment) {
comments.add(((ConfigNodeComment) node).commentText());
@ -376,14 +342,16 @@ final class ConfigParser {
values.add(v.withOrigin(v.origin().appendComments(new ArrayList<String>(comments))));
comments.clear();
}
pathStack.push(new Path(Integer.toString(index)));
index ++;
v = parseValue((AbstractConfigNodeValue)node, comments);
pathStack.pop();
}
}
// There shouldn't be any comments at this point, but add them just in case
if (v != null) {
values.add(v.withOrigin(v.origin().appendComments(new ArrayList<String>(comments))));
}
arrayCount -= 1;
return new SimpleConfigList(arrayOrigin, values);
}

View File

@ -55,7 +55,7 @@ final class ResolveSource {
}
}
static private ValueWithPath findInObject(AbstractConfigObject obj, Path path) {
static private ValueWithPath findInObject(AbstractConfigValue obj, Path path) {
try {
// we'll fail if anything along the path can't
// be looked at without resolving.
@ -65,19 +65,34 @@ final class ResolveSource {
}
}
static private ValueWithPath findInObject(AbstractConfigObject obj, Path path, Node<Container> parents) {
static private ValueWithPath findInObject(AbstractConfigValue obj, Path path, Node<Container> parents) {
String key = path.first();
Path next = path.remainder();
if (ConfigImpl.traceSubstitutionsEnabled())
ConfigImpl.trace("*** looking up '" + key + "' in " + obj);
AbstractConfigValue v = obj.attemptPeekWithPartialResolve(key);
Node<Container> newParents = parents == null ? new Node<Container>(obj) : parents.prepend(obj);
AbstractConfigValue v = null;
Node<Container> newParents = null;
if (obj instanceof AbstractConfigObject) {
v = ((AbstractConfigObject) obj).attemptPeekWithPartialResolve(key);
newParents = parents == null ? new Node<Container>((AbstractConfigObject) obj) : parents.prepend((AbstractConfigObject) obj);
} else if (obj instanceof SimpleConfigList) {
int ix;
try {
ix = Integer.parseInt(key);
} catch(Exception e) {
throw new ConfigException.BadPath(key, "path for list must be numeric index");
}
v = ((SimpleConfigList) obj).get(ix);
newParents = parents == null ? new Node<Container>((SimpleConfigList) obj) : parents.prepend((SimpleConfigList) obj);
}
if (next == null) {
return new ValueWithPath(v, newParents);
} else {
if (v instanceof AbstractConfigObject) {
return findInObject((AbstractConfigObject) v, next, newParents);
if (v instanceof AbstractConfigObject || v instanceof SimpleConfigList) {
return findInObject(v, next, newParents);
} else {
return new ValueWithPath(null, newParents);
}

View File

@ -1,7 +1,9 @@
package com.typesafe.config.impl;
import com.typesafe.config.*;
import com.typesafe.config.parser.ConfigDocument;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigException;
import java.io.StringReader;
import java.util.Iterator;
@ -32,9 +34,7 @@ final class SimpleConfigDocument implements ConfigDocument {
public ConfigDocument withValue(String path, ConfigValue newValue) {
if (newValue == null)
throw new ConfigException.BugOrBroken("null value for " + path + " passed to withValue");
ConfigRenderOptions options = ConfigRenderOptions.defaults();
options = options.setOriginComments(false);
return withValueText(path, newValue.render(options).trim());
return withValueText(path, newValue.render().trim());
}
@Override

View File

@ -38,7 +38,7 @@ final class Tokenizer {
return "tab";
else if (codepoint == -1)
return "end of file";
else if (ConfigImplUtil.isC0Control(codepoint))
else if (Character.isISOControl(codepoint))
return String.format("control character 0x%x", codepoint);
else
return String.format("%c", codepoint);
@ -498,7 +498,7 @@ final class Tokenizer {
} else if (c == '"') {
sbOrig.appendCodePoint(c);
break;
} else if (ConfigImplUtil.isC0Control(c)) {
} else if (Character.isISOControl(c)) {
throw problem(asString(c), "JSON does not allow unescaped " + asString(c)
+ " in quoted strings, use a backslash escape");
} else {

View File

@ -23,7 +23,6 @@ public class ArraysConfig {
List<ConfigValue> ofConfigValue;
List<Duration> ofDuration;
List<ConfigMemorySize> ofMemorySize;
List<StringsConfig> ofStringBean;
public List<Integer> getEmpty() {
return empty;
@ -128,12 +127,4 @@ public class ArraysConfig {
public void setOfMemorySize(List<ConfigMemorySize> ofMemorySize) {
this.ofMemorySize = ofMemorySize;
}
public List<StringsConfig> getOfStringBean() {
return ofStringBean;
}
public void setOfStringBean(List<StringsConfig> ofStringBean) {
this.ofStringBean = ofStringBean;
}
}

View File

@ -20,26 +20,4 @@ public class StringsConfig {
public void setYes(String s) {
yes = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof StringsConfig) {
StringsConfig sc = (StringsConfig) o;
return sc.abcd.equals(abcd) &&
sc.yes.equals(yes);
} else {
return false;
}
}
@Override
public int hashCode() {
int h = 41 * (41 + abcd.hashCode());
return h + yes.hashCode();
}
@Override
public String toString() {
return "StringsConfig(" + abcd + "," + yes + ")";
}
}

View File

@ -52,17 +52,7 @@
"ofConfigObject" : [${numbers}, ${booleans}, ${strings}],
"ofConfigValue" : [1, 2, "a"],
"ofDuration" : [1, 2h, 3 days],
"ofMemorySize" : [1024, 1M, 1G],
"ofStringBean" : [
{
abcd : "testAbcdOne"
yes : "testYesOne"
},
{
abcd : "testAbcdTwo"
yes : "testYesTwo"
}
]
"ofMemorySize" : [1024, 1M, 1G]
},
"bytes" : {
"kilobyte" : "1kB",

View File

@ -1,5 +1,10 @@
// The {} inside the [] is needed because
// just [ include ] means an array with the
// string "include" in it.
a = [ { include "test01.conf" } ]
a = [
{
include "test12.conf"
replace-key: "replaced"
}
]

View File

@ -0,0 +1,3 @@
// This file is included by include-from-list.conf to verify that includes with substitutions work in arrays
replace-me: ${replace-key}

View File

@ -345,26 +345,19 @@ class ConcatenationTest extends TestUtils {
assertEquals(Seq(1, 2, 3), conf.getObjectList("x.a").asScala.toList.map(_.toConfig.getInt("b")))
}
// We would ideally make this case NOT throw an exception but we need to do some work
// to get there, see https://github.com/typesafehub/config/issues/160
@Test
def plusEqualsMultipleTimesNestedInArray() {
val e = intercept[ConfigException.Parse] {
val conf = parseConfig("""x = [ { a += 1, a += 2, a += 3 } ] """).resolve()
assertEquals(Seq(1, 2, 3), conf.getObjectList("x").asScala.toVector(0).toConfig.getIntList("a").asScala.toList)
}
assertTrue(e.getMessage.contains("limitation"))
val conf = parseConfig("""x = [ { a += 1, a += 2, a += 3 } ] """).resolve()
assertEquals(Seq(1, 2, 3), conf.getObjectList("x").asScala.toVector(0).toConfig.getIntList("a").asScala.toList)
}
// We would ideally make this case NOT throw an exception but we need to do some work
// to get there, see https://github.com/typesafehub/config/issues/160
@Test
def plusEqualsMultipleTimesNestedInPlusEquals() {
val e = intercept[ConfigException.Parse] {
val e = intercept[ConfigException.BugOrBroken] {
val conf = parseConfig("""x += { a += 1, a += 2, a += 3 } """).resolve()
assertEquals(Seq(1, 2, 3), conf.getObjectList("x").asScala.toVector(0).toConfig.getIntList("a").asScala.toList)
}
assertTrue(e.getMessage.contains("limitation"))
assertTrue(e.getMessage.contains("did not find"))
}
// from https://github.com/typesafehub/config/issues/177

View File

@ -801,6 +801,14 @@ class ConfParserTest extends TestUtils {
assertTrue("including basename URL doesn't load anything", conf.isEmpty())
}
@Test
def includeWithSubstitutionsFromList() {
val conf = ConfigFactory.parseString("include file(" + jsonQuotedResourceFile("include-from-list") + ")")
val resolved = conf.resolve()
assertEquals(resolved.getConfigList("a").get(0).getString("replace-me"), "replaced")
}
@Test
def acceptBOMStartingFile() {
// BOM at start of file should be ignored

View File

@ -46,10 +46,10 @@ class ConfigBeanFactoryTest extends TestUtils {
ConfigBeanFactory.create(config, classOf[ValidationBeanConfig])
}
val expecteds = Seq(Missing("propNotListedInConfig", 77, "string"),
WrongType("shouldBeInt", 78, "number", "boolean"),
WrongType("should-be-boolean", 79, "boolean", "number"),
WrongType("should-be-list", 80, "list", "string"))
val expecteds = Seq(Missing("propNotListedInConfig", 67, "string"),
WrongType("shouldBeInt", 68, "number", "boolean"),
WrongType("should-be-boolean", 69, "boolean", "number"),
WrongType("should-be-list", 70, "list", "string"))
checkValidationException(e, expecteds)
}
@ -111,15 +111,6 @@ class ConfigBeanFactoryTest extends TestUtils {
ConfigMemorySize.ofBytes(1048576),
ConfigMemorySize.ofBytes(1073741824)),
beanConfig.getOfMemorySize.asScala)
val stringsConfigOne = new StringsConfig();
stringsConfigOne.setAbcd("testAbcdOne")
stringsConfigOne.setYes("testYesOne")
val stringsConfigTwo = new StringsConfig();
stringsConfigTwo.setAbcd("testAbcdTwo")
stringsConfigTwo.setYes("testYesTwo")
assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
}
@Test

View File

@ -171,14 +171,14 @@ class ConfigDocumentTest extends TestUtils {
@Test
def configDocumentSetNewValueMultiLevelConf {
val origText = "a:b\nc:d"
val finalText = "a:b\nc:d\ne : {\n f : {\n g : 12\n }\n}"
val finalText = "a:b\nc:d\ne : { f : { g : 12 } }"
configDocumentReplaceConfTest(origText, finalText, "12", "e.f.g")
}
@Test
def configDocumentSetNewValueMultiLevelJson {
val origText = "{\"a\":\"b\",\n\"c\":\"d\"}"
val finalText = "{\"a\":\"b\",\n\"c\":\"d\",\n \"e\" : {\n \"f\" : {\n \"g\" : 12\n }\n }}"
val finalText = "{\"a\":\"b\",\n\"c\":\"d\",\n\"e\" : { \"f\" : { \"g\" : 12 } }}"
configDocumentReplaceJsonTest(origText, finalText, "12", "e.f.g")
}
@ -342,13 +342,13 @@ class ConfigDocumentTest extends TestUtils {
var configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals("a {\n b: c\n e : f\n}", configDocument.withValueText("a.e", "f").render())
assertEquals("a {\n b: c\n d : {\n e : {\n f : g\n }\n }\n}", configDocument.withValueText("a.d.e.f", "g").render())
assertEquals("a {\n b: c\n d : { e : { f : g } }\n}", configDocument.withValueText("a.d.e.f", "g").render())
origText = "a {\n b: c\n}\n"
configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals("a {\n b: c\n}\nd : e\n", configDocument.withValueText("d", "e").render())
assertEquals("a {\n b: c\n}\nd : {\n e : {\n f : g\n }\n}\n", configDocument.withValueText("d.e.f", "g").render())
assertEquals("a {\n b: c\n}\nd : { e : { f : g } }\n", configDocument.withValueText("d.e.f", "g").render())
}
@Test
@ -435,14 +435,7 @@ class ConfigDocumentTest extends TestUtils {
val origText = ""
val configDocument = ConfigDocumentFactory.parseString(origText)
assertEquals("a : 1", configDocument.withValueText("a", "1").render)
val mapVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava)
assertEquals("a : {\n \"a\" : 1,\n \"b\" : 2\n}",
configDocument.withValue("a", mapVal).render)
val arrayVal = ConfigValueFactory.fromAnyRef(List(1, 2).asJava)
assertEquals("a : [\n 1,\n 2\n]", configDocument.withValue("a", arrayVal).render)
assertEquals(" a : 1", configDocument.withValueText("a", "1").render)
}
@Test
@ -452,7 +445,9 @@ class ConfigDocumentTest extends TestUtils {
val configVal = ConfigValueFactory.fromAnyRef(Map("a" -> 1, "b" -> 2).asJava)
assertEquals("{ a : {\n \"a\" : 1,\n \"b\" : 2\n } }",
assertEquals("{ a : {\n # hardcoded value\n \"a\" : 1,\n # hardcoded value\n \"b\" : 2\n } }",
configDocument.withValue("a", configVal).render)
}
}

View File

@ -212,7 +212,7 @@ class ConfigNodeTest extends TestUtils {
nodeCloseBrace))
assertEquals(origText, origNode.render())
val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" +
"def : false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\" : {\n\t\t end : doesnotexist\n\t\t}\n\t}\n}\n\nbaz.abc.ghi : randomunquotedString\n}"
"def : false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\" : { end : doesnotexist }\n\t}\n}\n\nbaz.abc.ghi : randomunquotedString\n}"
//Can replace settings in nested maps
// Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths

View File

@ -875,17 +875,6 @@ class PublicApiTest extends TestUtils {
assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("include statements nested"))
}
// We would ideally make this case NOT throw an exception but we need to do some work
// to get there, see https://github.com/typesafehub/config/issues/160
@Test
def detectIncludeFromList() {
val e = intercept[ConfigException.Parse] {
ConfigFactory.load("include-from-list.conf")
}
assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("limitation"))
}
@Test
def missingOverrideResourceFails() {
assertEquals("config.file is not set", null, System.getProperty("config.file"))