mirror of
https://github.com/lightbend/config.git
synced 2025-03-14 19:30:25 +08:00
conditional base classes
This commit is contained in:
parent
0755daa9db
commit
1d45880311
@ -0,0 +1,34 @@
|
||||
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("Substitutions in conditional expressions cannot be optional");
|
||||
}
|
||||
}
|
||||
|
||||
public SimpleConfigObject resolve(Map<String, AbstractConfigValue> values) {
|
||||
AbstractConfigValue val = values.get(left.path().first());
|
||||
if (val == null) {
|
||||
throw new ConfigException.BugOrBroken("Could not resolve substitution " + this.left.toString() + " in conditional expression");
|
||||
}
|
||||
if (val.equals(this.right)) {
|
||||
return this.body;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -289,6 +289,11 @@ final class ConfigDocumentParser {
|
||||
&& 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) {
|
||||
if (!Tokens.isUnquotedText(t))
|
||||
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) {
|
||||
Token t = nextTokenCollectingWhitespace(children);
|
||||
|
||||
@ -391,6 +437,11 @@ final class ConfigDocumentParser {
|
||||
includeNodes.add(new ConfigNodeSingleToken(t));
|
||||
objectNodes.add(parseInclude(includeNodes));
|
||||
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 {
|
||||
keyValueNodes = new ArrayList<AbstractConfigNode>();
|
||||
Token keyToken = t;
|
||||
|
@ -0,0 +1,41 @@
|
||||
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;
|
||||
}
|
||||
//
|
||||
// protected String name() {
|
||||
// for (AbstractConfigNode n : children) {
|
||||
// if (n instanceof ConfigNodeSimpleValue) {
|
||||
// return (String)Tokens.getValue(((ConfigNodeSimpleValue) n).token()).unwrapped();
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ final class ConfigParser {
|
||||
} else if (n instanceof ConfigNodeObject) {
|
||||
v = parseObject((ConfigNodeObject)n);
|
||||
} else if (n instanceof ConfigNodeArray) {
|
||||
v = parseArray((ConfigNodeArray)n);
|
||||
v = parseArray((ConfigNodeArray) n);
|
||||
} else if (n instanceof ConfigNodeConcatenation) {
|
||||
v = parseConcatenation((ConfigNodeConcatenation)n);
|
||||
} else {
|
||||
@ -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) {
|
||||
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
|
||||
SimpleConfigOrigin objectOrigin = lineOrigin();
|
||||
boolean lastWasNewline = false;
|
||||
List<ConfigConditional> conditionals = new ArrayList<ConfigConditional>();
|
||||
|
||||
ArrayList<AbstractConfigNode> nodes = new ArrayList<AbstractConfigNode>(n.children());
|
||||
List<String> comments = new ArrayList<String>();
|
||||
@ -212,8 +241,10 @@ final class ConfigParser {
|
||||
}
|
||||
lastWasNewline = true;
|
||||
} else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeInclude) {
|
||||
parseInclude(values, (ConfigNodeInclude)node);
|
||||
parseInclude(values, (ConfigNodeInclude) node);
|
||||
lastWasNewline = false;
|
||||
} else if (flavor != ConfigSyntax.JSON && node instanceof ConfigNodeConditional) {
|
||||
conditionals.add(parseConditional((ConfigNodeConditional) node));
|
||||
} else if (node instanceof ConfigNodeField) {
|
||||
lastWasNewline = false;
|
||||
Path path = ((ConfigNodeField) node).path().value();
|
||||
|
@ -28,12 +28,15 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
||||
|
||||
// this map should never be modified - assume immutable
|
||||
final private Map<String, AbstractConfigValue> value;
|
||||
final private List<ConfigConditional> conditionals;
|
||||
final private boolean resolved;
|
||||
final private boolean ignoresFallbacks;
|
||||
|
||||
SimpleConfigObject(ConfigOrigin origin,
|
||||
Map<String, AbstractConfigValue> value, ResolveStatus status,
|
||||
boolean ignoresFallbacks) {
|
||||
Map<String, AbstractConfigValue> value,
|
||||
ResolveStatus status,
|
||||
boolean ignoresFallbacks,
|
||||
List<ConfigConditional> conditionals) {
|
||||
super(origin);
|
||||
if (value == null)
|
||||
throw new ConfigException.BugOrBroken(
|
||||
@ -41,15 +44,29 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
||||
this.value = value;
|
||||
this.resolved = status == ResolveStatus.RESOLVED;
|
||||
this.ignoresFallbacks = ignoresFallbacks;
|
||||
this.conditionals = conditionals;
|
||||
|
||||
// Kind of an expensive debug check. Comment out?
|
||||
if (status != ResolveStatus.fromValues(value.values()))
|
||||
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,
|
||||
Map<String, AbstractConfigValue> value) {
|
||||
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */);
|
||||
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, new ArrayList<ConfigConditional>());
|
||||
}
|
||||
|
||||
SimpleConfigObject(ConfigOrigin origin,
|
||||
Map<String, AbstractConfigValue> value,
|
||||
List<ConfigConditional> conditionals) {
|
||||
this(origin, value, ResolveStatus.fromValues(value.values()), false /* ignoresFallbacks */, conditionals);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
14
config/src/test/resources/conditional.conf
Normal file
14
config/src/test/resources/conditional.conf
Normal file
@ -0,0 +1,14 @@
|
||||
shouldDoIt: true
|
||||
a: {
|
||||
if [${shouldDoIt} == true] {
|
||||
b: "b"
|
||||
c: {
|
||||
d: "d"
|
||||
}
|
||||
}
|
||||
x: {
|
||||
if [${shouldDoIt} == false] {
|
||||
e: "e"
|
||||
}
|
||||
}
|
||||
}
|
@ -809,6 +809,18 @@ class ConfParserTest extends TestUtils {
|
||||
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").getConfig("c").getString("d"), "d")
|
||||
intercept[Exception] {
|
||||
resolved.getConfig("a").getString("e")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def acceptBOMStartingFile() {
|
||||
// BOM at start of file should be ignored
|
||||
|
@ -62,6 +62,7 @@ class ConfigDocumentParserTest extends TestUtils {
|
||||
parseTest("foo:bar")
|
||||
parseTest(" foo : bar ")
|
||||
parseTest("""include "foo.conf" """)
|
||||
parseTest("if [${foo} == bar] { key: value }");
|
||||
parseTest(" \nfoo:bar\n ")
|
||||
|
||||
// Can parse a map with all simple types
|
||||
|
Loading…
Reference in New Issue
Block a user