mirror of
https://github.com/lightbend/config.git
synced 2025-03-22 23:30:27 +08:00
commit
a6f51ea8a7
18
HOCON.md
18
HOCON.md
@ -26,6 +26,7 @@
|
|||||||
- [Self-Referential Substitutions](#self-referential-substitutions)
|
- [Self-Referential Substitutions](#self-referential-substitutions)
|
||||||
- [The `+=` field separator](#the--field-separator)
|
- [The `+=` field separator](#the--field-separator)
|
||||||
- [Examples of Self-Referential Substitutions](#examples-of-self-referential-substitutions)
|
- [Examples of Self-Referential Substitutions](#examples-of-self-referential-substitutions)
|
||||||
|
- [Conditional expressions](#conditional-expressions)
|
||||||
- [Includes](#includes)
|
- [Includes](#includes)
|
||||||
- [Include syntax](#include-syntax)
|
- [Include syntax](#include-syntax)
|
||||||
- [Include semantics: merging](#include-semantics-merging)
|
- [Include semantics: merging](#include-semantics-merging)
|
||||||
@ -895,6 +896,23 @@ rather than by the path inside the `${}` expression, because
|
|||||||
substitutions may be resolved differently depending on their
|
substitutions may be resolved differently depending on their
|
||||||
position in the file.
|
position in the file.
|
||||||
|
|
||||||
|
### Conditional expressions
|
||||||
|
|
||||||
|
Conditional expressions allow for a block of configuration to be
|
||||||
|
included or omitted based on the value of a substitution.
|
||||||
|
|
||||||
|
Example conditional expression:
|
||||||
|
|
||||||
|
- `if [${a} == "a"] { b: true }`
|
||||||
|
|
||||||
|
In this case, if the substitution ${a} was equal to the string "a"
|
||||||
|
then the object { b: true } would be merged into the object that
|
||||||
|
the conditional expression was declared inside. If it was not
|
||||||
|
equal to "a" nothing would be merged.
|
||||||
|
|
||||||
|
The left hand side substitution cannot be optional. Currently, only equality comparisons are supported. The right hand side
|
||||||
|
of the expression can be any string, quoted or unquoted, or a boolean.
|
||||||
|
|
||||||
### Includes
|
### Includes
|
||||||
|
|
||||||
#### Include syntax
|
#### Include syntax
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.typesafe.config.impl;
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigException;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ConfigConditional {
|
||||||
|
|
||||||
|
private SubstitutionExpression left;
|
||||||
|
private AbstractConfigValue right;
|
||||||
|
private SimpleConfigObject body;
|
||||||
|
|
||||||
|
ConfigConditional(SubstitutionExpression left, AbstractConfigValue right, SimpleConfigObject body) {
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
this.body = body;
|
||||||
|
|
||||||
|
if (this.left.optional()) {
|
||||||
|
throw new ConfigException.BugOrBroken("Substitution " + this.left.toString() + " in conditional expression cannot be optional");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleConfigObject resolve(ResolveContext context, ResolveSource source) {
|
||||||
|
try {
|
||||||
|
AbstractConfigValue val = source.lookupSubst(context, this.left, 0).result.value;
|
||||||
|
|
||||||
|
if (val.equals(this.right)) {
|
||||||
|
return this.body;
|
||||||
|
} else {
|
||||||
|
return SimpleConfigObject.empty();
|
||||||
|
}
|
||||||
|
} catch (AbstractConfigValue.NotPossibleToResolve e){
|
||||||
|
throw new ConfigException.BugOrBroken("Could not resolve left side of conditional expression: " + this.left.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -289,6 +289,11 @@ final class ConfigDocumentParser {
|
|||||||
&& Tokens.getUnquotedText(t).equals("include");
|
&& Tokens.getUnquotedText(t).equals("include");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isIfKeyword(Token t) {
|
||||||
|
return Tokens.isUnquotedText(t)
|
||||||
|
&& Tokens.getUnquotedText(t).equals("if");
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isUnquotedWhitespace(Token t) {
|
private static boolean isUnquotedWhitespace(Token t) {
|
||||||
if (!Tokens.isUnquotedText(t))
|
if (!Tokens.isUnquotedText(t))
|
||||||
return false;
|
return false;
|
||||||
@ -311,6 +316,47 @@ final class ConfigDocumentParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfigNodeConditional parseConditional(ArrayList<AbstractConfigNode> children) {
|
||||||
|
Token openToken = nextTokenCollectingWhitespace(children);
|
||||||
|
if (openToken != Tokens.OPEN_SQUARE)
|
||||||
|
throw parseError("expecting [ after if");
|
||||||
|
children.add(new ConfigNodeSingleToken(openToken));
|
||||||
|
|
||||||
|
Token subToken = nextTokenCollectingWhitespace(children);
|
||||||
|
if (!Tokens.isSubstitution(subToken))
|
||||||
|
throw parseError("left side of conditional expression must be a variable substitution");
|
||||||
|
children.add(new ConfigNodeSingleToken(subToken));
|
||||||
|
|
||||||
|
Token eqToken = nextTokenCollectingWhitespace(children);
|
||||||
|
Token eq2Token = nextTokenCollectingWhitespace(children);
|
||||||
|
if (eqToken != Tokens.EQUALS || eq2Token != Tokens.EQUALS)
|
||||||
|
throw parseError("conditional check must be `==`");
|
||||||
|
children.add(new ConfigNodeSingleToken(eqToken));
|
||||||
|
children.add(new ConfigNodeSingleToken(eq2Token));
|
||||||
|
|
||||||
|
Token valToken = nextTokenCollectingWhitespace(children);
|
||||||
|
if (!Tokens.isValueWithType(valToken, ConfigValueType.STRING)
|
||||||
|
&& !Tokens.isUnquotedText(valToken)
|
||||||
|
&& !Tokens.isValueWithType(valToken, ConfigValueType.BOOLEAN))
|
||||||
|
throw parseError("right side of conditional expression must be a string or boolean");
|
||||||
|
children.add(new ConfigNodeSimpleValue(valToken));
|
||||||
|
|
||||||
|
Token closeToken = nextTokenCollectingWhitespace(children);
|
||||||
|
if (closeToken != Tokens.CLOSE_SQUARE)
|
||||||
|
throw parseError("expecting ] after conditional expression");
|
||||||
|
children.add(new ConfigNodeSingleToken(closeToken));
|
||||||
|
|
||||||
|
Token openCurlyToken = nextTokenCollectingWhitespace(children);
|
||||||
|
if (openCurlyToken != Tokens.OPEN_CURLY)
|
||||||
|
throw parseError("must open a conditional body using {");
|
||||||
|
|
||||||
|
ArrayList<AbstractConfigNode> importantChildren = new ArrayList<AbstractConfigNode>(children);
|
||||||
|
|
||||||
|
ConfigNodeComplexValue body = parseObject(true);
|
||||||
|
|
||||||
|
return new ConfigNodeConditional(importantChildren, body);
|
||||||
|
}
|
||||||
|
|
||||||
private ConfigNodeInclude parseInclude(ArrayList<AbstractConfigNode> children) {
|
private ConfigNodeInclude parseInclude(ArrayList<AbstractConfigNode> children) {
|
||||||
Token t = nextTokenCollectingWhitespace(children);
|
Token t = nextTokenCollectingWhitespace(children);
|
||||||
|
|
||||||
@ -391,6 +437,11 @@ final class ConfigDocumentParser {
|
|||||||
includeNodes.add(new ConfigNodeSingleToken(t));
|
includeNodes.add(new ConfigNodeSingleToken(t));
|
||||||
objectNodes.add(parseInclude(includeNodes));
|
objectNodes.add(parseInclude(includeNodes));
|
||||||
afterComma = false;
|
afterComma = false;
|
||||||
|
} else if (flavor != ConfigSyntax.JSON && isIfKeyword(t)) {
|
||||||
|
ArrayList<AbstractConfigNode> ifNodes = new ArrayList<AbstractConfigNode>();
|
||||||
|
ifNodes.add(new ConfigNodeSingleToken(t));
|
||||||
|
objectNodes.add(parseConditional(ifNodes));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
keyValueNodes = new ArrayList<AbstractConfigNode>();
|
keyValueNodes = new ArrayList<AbstractConfigNode>();
|
||||||
Token keyToken = t;
|
Token keyToken = t;
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.typesafe.config.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class ConfigNodeConditional extends AbstractConfigNode {
|
||||||
|
final private ArrayList<AbstractConfigNode> children;
|
||||||
|
final private AbstractConfigNodeValue body;
|
||||||
|
|
||||||
|
ConfigNodeConditional(Collection<AbstractConfigNode> children, AbstractConfigNodeValue body) {
|
||||||
|
this.children = new ArrayList<AbstractConfigNode>(children);
|
||||||
|
this.body = body;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractConfigNodeValue body() { return body; }
|
||||||
|
public List<AbstractConfigNode> children() { return children; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Token> tokens() {
|
||||||
|
ArrayList<Token> tokens = new ArrayList<Token>();
|
||||||
|
for (AbstractConfigNode child : children) {
|
||||||
|
tokens.addAll(child.tokens());
|
||||||
|
}
|
||||||
|
for (Token token : body.tokens()) {
|
||||||
|
tokens.add(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -192,10 +192,39 @@ final class ConfigParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfigConditional parseConditional(ConfigNodeConditional n) {
|
||||||
|
SubstitutionExpression left = null;
|
||||||
|
AbstractConfigValue right = null;
|
||||||
|
|
||||||
|
AbstractConfigObject body = parseObject((ConfigNodeObject) n.body());
|
||||||
|
|
||||||
|
for(AbstractConfigNode child: n.children()) {
|
||||||
|
if (child instanceof ConfigNodeSingleToken) {
|
||||||
|
Token token = ((ConfigNodeSingleToken) child).token();
|
||||||
|
|
||||||
|
if (Tokens.isSubstitution(token)) {
|
||||||
|
List<Token> pathTokens = Tokens.getSubstitutionPathExpression(token);
|
||||||
|
Path path = PathParser.parsePathExpression(pathTokens.iterator(), token.origin());
|
||||||
|
left = new SubstitutionExpression(path, false);
|
||||||
|
}
|
||||||
|
} else if (child instanceof AbstractConfigNodeValue) {
|
||||||
|
right = parseValue((AbstractConfigNodeValue)child, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left == null)
|
||||||
|
throw new ConfigException.BugOrBroken("Conditional expression did not have a left hand substitution");
|
||||||
|
if (right == null)
|
||||||
|
throw new ConfigException.BugOrBroken("Conditional expression did not have a right hand value");
|
||||||
|
|
||||||
|
return new ConfigConditional(left, right, (SimpleConfigObject) body);
|
||||||
|
}
|
||||||
|
|
||||||
private AbstractConfigObject parseObject(ConfigNodeObject n) {
|
private AbstractConfigObject parseObject(ConfigNodeObject n) {
|
||||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||||
SimpleConfigOrigin objectOrigin = lineOrigin();
|
SimpleConfigOrigin objectOrigin = lineOrigin();
|
||||||
boolean lastWasNewline = false;
|
boolean lastWasNewline = false;
|
||||||
|
List<ConfigConditional> conditionals = new ArrayList<ConfigConditional>();
|
||||||
|
|
||||||
ArrayList<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>(n.children());
|
ArrayList<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>(n.children());
|
||||||
List<String> comments = new ArrayList<String>();
|
List<String> comments = new ArrayList<String>();
|
||||||
@ -212,8 +241,10 @@ final class ConfigParser {
|
|||||||
}
|
}
|
||||||
lastWasNewline = true;
|
lastWasNewline = true;
|
||||||
} else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) {
|
} else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) {
|
||||||
parseInclude(values, (ConfigNodeInclude)node);
|
parseInclude(values, (ConfigNodeInclude) node);
|
||||||
lastWasNewline = false;
|
lastWasNewline = false;
|
||||||
|
} else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeConditional) {
|
||||||
|
conditionals.add(parseConditional((ConfigNodeConditional) node));
|
||||||
} else if (node instanceof ConfigNodeField) {
|
} else if (node instanceof ConfigNodeField) {
|
||||||
lastWasNewline = false;
|
lastWasNewline = false;
|
||||||
Path path = ((ConfigNodeField) node).path().value();
|
Path path = ((ConfigNodeField) node).path().value();
|
||||||
@ -307,7 +338,7 @@ final class ConfigParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleConfigObject(objectOrigin, values);
|
return new SimpleConfigObject(objectOrigin, values, conditionals);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SimpleConfigList parseArray(ConfigNodeArray n) {
|
private SimpleConfigList parseArray(ConfigNodeArray n) {
|
||||||
|
@ -12,11 +12,13 @@ enum ResolveStatus {
|
|||||||
UNRESOLVED, RESOLVED;
|
UNRESOLVED, RESOLVED;
|
||||||
|
|
||||||
final static ResolveStatus fromValues(
|
final static ResolveStatus fromValues(
|
||||||
Collection<? extends AbstractConfigValue> values) {
|
Collection<? extends AbstractConfigValue> values, Collection<ConfigConditional> conditionals) {
|
||||||
for (AbstractConfigValue v : values) {
|
for (AbstractConfigValue v : values) {
|
||||||
if (v.resolveStatus() == ResolveStatus.UNRESOLVED)
|
if (v.resolveStatus() == ResolveStatus.UNRESOLVED)
|
||||||
return ResolveStatus.UNRESOLVED;
|
return ResolveStatus.UNRESOLVED;
|
||||||
}
|
}
|
||||||
|
if (conditionals.size() > 0)
|
||||||
|
return ResolveStatus.UNRESOLVED;
|
||||||
return ResolveStatus.RESOLVED;
|
return ResolveStatus.RESOLVED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +26,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
|
|||||||
final private boolean resolved;
|
final private boolean resolved;
|
||||||
|
|
||||||
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) {
|
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value) {
|
||||||
this(origin, value, ResolveStatus
|
this(origin, value, ResolveStatus.fromValues(value, new ArrayList<ConfigConditional>()));
|
||||||
.fromValues(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value,
|
SimpleConfigList(ConfigOrigin origin, List<AbstractConfigValue> value,
|
||||||
@ -37,7 +36,7 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList,
|
|||||||
this.resolved = status == ResolveStatus.RESOLVED;
|
this.resolved = status == ResolveStatus.RESOLVED;
|
||||||
|
|
||||||
// kind of an expensive debug check (makes this constructor pointless)
|
// kind of an expensive debug check (makes this constructor pointless)
|
||||||
if (status != ResolveStatus.fromValues(value))
|
if (status != ResolveStatus.fromValues(value, new ArrayList<ConfigConditional>()))
|
||||||
throw new ConfigException.BugOrBroken(
|
throw new ConfigException.BugOrBroken(
|
||||||
"SimpleConfigList created with wrong resolve status: " + this);
|
"SimpleConfigList created with wrong resolve status: " + this);
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,15 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
|
|
||||||
// this map should never be modified - assume immutable
|
// this map should never be modified - assume immutable
|
||||||
final private Map<String, AbstractConfigValue> value;
|
final private Map<String, AbstractConfigValue> value;
|
||||||
|
final private Collection<ConfigConditional> conditionals;
|
||||||
final private boolean resolved;
|
final private boolean resolved;
|
||||||
final private boolean ignoresFallbacks;
|
final private boolean ignoresFallbacks;
|
||||||
|
|
||||||
SimpleConfigObject(ConfigOrigin origin,
|
SimpleConfigObject(ConfigOrigin origin,
|
||||||
Map<String, AbstractConfigValue> value, ResolveStatus status,
|
Map<String, AbstractConfigValue> value,
|
||||||
boolean ignoresFallbacks) {
|
ResolveStatus status,
|
||||||
|
boolean ignoresFallbacks,
|
||||||
|
Collection<ConfigConditional> conditionals) {
|
||||||
super(origin);
|
super(origin);
|
||||||
if (value == null)
|
if (value == null)
|
||||||
throw new ConfigException.BugOrBroken(
|
throw new ConfigException.BugOrBroken(
|
||||||
@ -41,15 +44,29 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
this.value = value;
|
this.value = value;
|
||||||
this.resolved = status == ResolveStatus.RESOLVED;
|
this.resolved = status == ResolveStatus.RESOLVED;
|
||||||
this.ignoresFallbacks = ignoresFallbacks;
|
this.ignoresFallbacks = ignoresFallbacks;
|
||||||
|
this.conditionals = conditionals;
|
||||||
|
|
||||||
// Kind of an expensive debug check. Comment out?
|
// Kind of an expensive debug check. Comment out?
|
||||||
if (status != ResolveStatus.fromValues(value.values()))
|
if (status != ResolveStatus.fromValues(value.values(), conditionals))
|
||||||
throw new ConfigException.BugOrBroken("Wrong resolved status on " + this);
|
throw new ConfigException.BugOrBroken("Wrong resolved status on " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimpleConfigObject(ConfigOrigin origin,
|
||||||
|
Map<String, AbstractConfigValue> value,
|
||||||
|
ResolveStatus resolveStatus,
|
||||||
|
boolean ignoresFallbacks) {
|
||||||
|
this(origin, value, resolveStatus, ignoresFallbacks, new ArrayList<ConfigConditional>());
|
||||||
|
}
|
||||||
|
|
||||||
SimpleConfigObject(ConfigOrigin origin,
|
SimpleConfigObject(ConfigOrigin origin,
|
||||||
Map<String, AbstractConfigValue> value) {
|
Map<String, AbstractConfigValue> value) {
|
||||||
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */);
|
this(origin, value, ResolveStatus.fromValues(value.values(), new ArrayList<ConfigConditional>()), false /* ignoresFallbacks */, new ArrayList<ConfigConditional>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleConfigObject(ConfigOrigin origin,
|
||||||
|
Map<String, AbstractConfigValue> value,
|
||||||
|
List<ConfigConditional> conditionals) {
|
||||||
|
this(origin, value, ResolveStatus.fromValues(value.values(), conditionals), false /* ignoresFallbacks */, conditionals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -114,8 +131,8 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
Map<String, AbstractConfigValue> updated = new HashMap<String, AbstractConfigValue>(
|
Map<String, AbstractConfigValue> updated = new HashMap<String, AbstractConfigValue>(
|
||||||
value);
|
value);
|
||||||
updated.put(key, v);
|
updated.put(key, v);
|
||||||
return new SimpleConfigObject(origin(), updated, ResolveStatus.fromValues(updated
|
return new SimpleConfigObject(origin(), updated,
|
||||||
.values()), ignoresFallbacks);
|
ResolveStatus.fromValues(updated.values(), conditionals), ignoresFallbacks);
|
||||||
} else if (next != null || v == null) {
|
} else if (next != null || v == null) {
|
||||||
// can't descend, nothing to remove
|
// can't descend, nothing to remove
|
||||||
return this;
|
return this;
|
||||||
@ -126,8 +143,8 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
if (!old.getKey().equals(key))
|
if (!old.getKey().equals(key))
|
||||||
smaller.put(old.getKey(), old.getValue());
|
smaller.put(old.getKey(), old.getValue());
|
||||||
}
|
}
|
||||||
return new SimpleConfigObject(origin(), smaller, ResolveStatus.fromValues(smaller
|
return new SimpleConfigObject(origin(), smaller,
|
||||||
.values()), ignoresFallbacks);
|
ResolveStatus.fromValues(smaller.values(), conditionals), ignoresFallbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +162,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
newMap.put(key, (AbstractConfigValue) v);
|
newMap.put(key, (AbstractConfigValue) v);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values()),
|
return new SimpleConfigObject(origin(), newMap, ResolveStatus.fromValues(newMap.values(), conditionals),
|
||||||
ignoresFallbacks);
|
ignoresFallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,13 +193,13 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin,
|
private SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin,
|
||||||
boolean newIgnoresFallbacks) {
|
boolean newIgnoresFallbacks, Collection<ConfigConditional> conditionals) {
|
||||||
return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks);
|
return new SimpleConfigObject(newOrigin, value, newStatus, newIgnoresFallbacks, conditionals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin) {
|
protected SimpleConfigObject newCopy(ResolveStatus newStatus, ConfigOrigin newOrigin) {
|
||||||
return newCopy(newStatus, newOrigin, ignoresFallbacks);
|
return newCopy(newStatus, newOrigin, ignoresFallbacks, conditionals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -190,7 +207,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
if (ignoresFallbacks)
|
if (ignoresFallbacks)
|
||||||
return this;
|
return this;
|
||||||
else
|
else
|
||||||
return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */);
|
return newCopy(resolveStatus(), origin(), true /* ignoresFallbacks */, conditionals);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -208,7 +225,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
else
|
else
|
||||||
newChildren.remove(old.getKey());
|
newChildren.remove(old.getKey());
|
||||||
|
|
||||||
return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values()),
|
return new SimpleConfigObject(origin(), newChildren, ResolveStatus.fromValues(newChildren.values(), conditionals),
|
||||||
ignoresFallbacks);
|
ignoresFallbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,7 +305,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus,
|
return new SimpleConfigObject(mergeOrigins(this, fallback), merged, newResolveStatus,
|
||||||
newIgnoresFallbacks);
|
newIgnoresFallbacks);
|
||||||
else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks())
|
else if (newResolveStatus != resolveStatus() || newIgnoresFallbacks != ignoresFallbacks())
|
||||||
return newCopy(newResolveStatus, origin(), newIgnoresFallbacks);
|
return newCopy(newResolveStatus, origin(), newIgnoresFallbacks, new ArrayList<ConfigConditional>());
|
||||||
else
|
else
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -305,6 +322,7 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
|
|
||||||
private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception {
|
private SimpleConfigObject modifyMayThrow(Modifier modifier) throws Exception {
|
||||||
Map<String, AbstractConfigValue> changes = null;
|
Map<String, AbstractConfigValue> changes = null;
|
||||||
|
|
||||||
for (String k : keySet()) {
|
for (String k : keySet()) {
|
||||||
AbstractConfigValue v = value.get(k);
|
AbstractConfigValue v = value.get(k);
|
||||||
// "modified" may be null, which means remove the child;
|
// "modified" may be null, which means remove the child;
|
||||||
@ -396,6 +414,13 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
|||||||
ResolveModifier modifier = new ResolveModifier(context, sourceWithParent);
|
ResolveModifier modifier = new ResolveModifier(context, sourceWithParent);
|
||||||
|
|
||||||
AbstractConfigValue value = modifyMayThrow(modifier);
|
AbstractConfigValue value = modifyMayThrow(modifier);
|
||||||
|
|
||||||
|
for (ConfigConditional cond: this.conditionals) {
|
||||||
|
SimpleConfigObject body = cond.resolve(context, sourceWithParent);
|
||||||
|
AbstractConfigObject resolvedBody = body.resolveSubstitutions(context, source).value;
|
||||||
|
value = value.mergedWithObject(resolvedBody);
|
||||||
|
}
|
||||||
|
|
||||||
return ResolveResult.make(modifier.context, value).asObjectResult();
|
return ResolveResult.make(modifier.context, value).asObjectResult();
|
||||||
} catch (NotPossibleToResolve e) {
|
} catch (NotPossibleToResolve e) {
|
||||||
throw e;
|
throw e;
|
||||||
|
19
config/src/test/resources/conditional.conf
Normal file
19
config/src/test/resources/conditional.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
shouldDoIt: true
|
||||||
|
a: {
|
||||||
|
if [${shouldDoIt} == true] {
|
||||||
|
b: "b"
|
||||||
|
}
|
||||||
|
if [${shouldDoIt} == true] {
|
||||||
|
c: "c"
|
||||||
|
f: ${a.b}
|
||||||
|
nested: {
|
||||||
|
n: "n"
|
||||||
|
if [${a.c} == "c"] {
|
||||||
|
works: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if [${shouldDoIt} == false] {
|
||||||
|
d: "d"
|
||||||
|
}
|
||||||
|
}
|
@ -809,6 +809,21 @@ class ConfParserTest extends TestUtils {
|
|||||||
assertEquals(resolved.getConfigList("a").get(0).getString("replace-me"), "replaced")
|
assertEquals(resolved.getConfigList("a").get(0).getString("replace-me"), "replaced")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
def conditionals() {
|
||||||
|
val conf = ConfigFactory.parseResources("conditional.conf")
|
||||||
|
val resolved = conf.resolve()
|
||||||
|
|
||||||
|
assertEquals(resolved.getConfig("a").getString("b"), "b")
|
||||||
|
assertEquals(resolved.getConfig("a").getString("c"), "c")
|
||||||
|
assertEquals(resolved.getConfig("a").getString("f"), "b")
|
||||||
|
|
||||||
|
assertEquals(resolved.getConfig("a").getConfig("nested").getBoolean("works"), true)
|
||||||
|
intercept[Exception] {
|
||||||
|
resolved.getConfig("a").getConfig("d")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
def acceptBOMStartingFile() {
|
def acceptBOMStartingFile() {
|
||||||
// BOM at start of file should be ignored
|
// BOM at start of file should be ignored
|
||||||
|
@ -62,6 +62,7 @@ class ConfigDocumentParserTest extends TestUtils {
|
|||||||
parseTest("foo:bar")
|
parseTest("foo:bar")
|
||||||
parseTest(" foo : bar ")
|
parseTest(" foo : bar ")
|
||||||
parseTest("""include "foo.conf" """)
|
parseTest("""include "foo.conf" """)
|
||||||
|
parseTest("if [${foo} == bar] { key: value }");
|
||||||
parseTest(" \nfoo:bar\n ")
|
parseTest(" \nfoo:bar\n ")
|
||||||
|
|
||||||
// Can parse a map with all simple types
|
// Can parse a map with all simple types
|
||||||
|
Loading…
Reference in New Issue
Block a user