Implement include keyword

This commit is contained in:
Havoc Pennington 2011-11-12 09:26:57 -05:00
parent 5a51d590af
commit 145cb40ec2
4 changed files with 92 additions and 0 deletions

View File

@ -326,6 +326,51 @@ final class Parser {
}
}
private static boolean isIncludeKeyword(Token t) {
return Tokens.isUnquotedText(t)
&& Tokens.getUnquotedText(t).equals("include");
}
private static boolean isUnquotedWhitespace(Token t) {
if (!Tokens.isUnquotedText(t))
return false;
String s = Tokens.getUnquotedText(t);
for (int i = 0; i < s.length(); ++i) {
char c = s.charAt(i);
if (!Character.isWhitespace(c))
return false;
}
return true;
}
private void parseInclude(Map<String, AbstractConfigValue> values) {
Token t = nextTokenIgnoringNewline();
while (isUnquotedWhitespace(t)) {
t = nextTokenIgnoringNewline();
}
if (Tokens.isValueWithType(t, ConfigValueType.STRING)) {
String name = (String) Tokens.getValue(t).unwrapped();
AbstractConfigObject obj = includer.include(name);
for (String key : obj.keySet()) {
AbstractConfigValue v = obj.get(key);
AbstractConfigValue existing = values.get(key);
if (existing != null) {
values.put(key, v.withFallback(existing));
} else {
values.put(key, v);
}
}
} else {
throw parseError("include keyword is not followed by a quoted string, but by: "
+ t);
}
}
private AbstractConfigObject parseObject() {
// invoked just after the OPEN_CURLY
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
@ -338,6 +383,10 @@ final class Parser {
throw parseError("expecting a field name after comma, got a close brace }");
}
break;
} else if (flavor != SyntaxFlavor.JSON && isIncludeKeyword(t)) {
parseInclude(values);
afterComma = false;
} else {
Path path = parseKey(t);
Token afterKey = nextTokenIgnoringNewline();

View File

@ -0,0 +1,17 @@
{
"test01" : {
"ints" : 12,
include "test01",
"booleans" : 42
},
"test02" : {
include
"test02.conf"
},
"equiv01" : {
include "equiv01/original.json"
}
}

View File

@ -634,4 +634,22 @@ class ConfigTest extends TestUtils {
assertEquals(57, conf.getInt(""" "a"."b"."c" """))
assertEquals(103, conf.getInt(""" "a.b.c" """))
}
@Test
def test03Includes() {
val conf = Config.load("test03")
// include should have overridden the "ints" value in test03
assertEquals(42, conf.getInt("test01.ints.fortyTwo"))
// include should have been overridden by 42
assertEquals(42, conf.getInt("test01.booleans"));
assertEquals(42, conf.getInt("test01.booleans"));
// include should have gotten .properties and .json also
assertEquals("abc", conf.getString("test01.fromProps.abc"))
assertEquals("A", conf.getString("test01.fromJsonA"))
// test02 was included
assertEquals(57, conf.getInt("test02.a.b.c"))
// equiv01/original.json was included (it has a slash in the name)
assertEquals("a", conf.getString("equiv01.strings.a"))
}
}

View File

@ -134,6 +134,9 @@ abstract trait TestUtils {
"""{ "a" : ${a} }""", // simple cycle
"""[ { "a" : 2, "b" : ${${a}} } ]""", // nested substitution
"[ = ]", // = is not a valid token
"{ include \"bar\" : 10 }", // include with a value after it
"{ include foo }", // include with unquoted string
"{ include : { \"a\" : 1 } }", // include used as unquoted key
"") // empty document again, just for clean formatting of this list ;-)
// We'll automatically try each of these with whitespace modifications
@ -178,6 +181,11 @@ abstract trait TestUtils {
"[ trux ]",
"[ truex ]",
"[ 10x ]", // number token with trailing junk
"{ include \"foo\" }", // valid include
"{ include\n\"foo\" }", // include with just a newline separating from string
"{ include\"foo\" }", // include with no whitespace after it
"[ include ]", // include can be a string value in an array
"{ foo : include }", // include can be a field value also
"[ ${foo} ]",
"[ ${\"foo\"} ]",
"[ ${foo.bar} ]",