mirror of
https://github.com/lightbend/config.git
synced 2025-03-14 19:30:25 +08:00
Support variable substitution in includes
This commit is contained in:
parent
aac122ae01
commit
730ad27628
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.classpath
|
||||
.project
|
||||
.cache
|
||||
.DS_Store
|
||||
.settings
|
||||
.idea
|
||||
.idea_modules
|
||||
|
@ -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
|
13
HOCON.md
13
HOCON.md
@ -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
10
NEWS.md
@ -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
|
||||
|
21
README.md
21
README.md
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 + ")";
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
|
3
config/src/test/resources/test12.conf
Normal file
3
config/src/test/resources/test12.conf
Normal 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}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"))
|
||||
|
Loading…
Reference in New Issue
Block a user