Address further PR feedback before merge

Implement an `equals()` method for AbstractConfigNodes. Add a
test to ensure an exception is thrown when calling setValue()
on a ConfigDocument when passing in a value with HOCON syntax
when the document was parsed as JSON. Add a setValue() method to
the ConfigDocument interface that takes a ConfigValue instead of
a String. When parsing a single value, throw an exception if a
concatenation is seen when parsing JSON.
This commit is contained in:
Preben Ingvaldsen 2015-03-23 15:41:13 -07:00
parent c3a2e07c6a
commit 1639a91481
8 changed files with 271 additions and 181 deletions

View File

@ -25,12 +25,29 @@ public interface ConfigDocument {
*
* @param path the path at which to set the desired value
* @param newValue the value to set at the desired path, represented as a string. This
* string will be parsed into a ConfigNode, and the text will be inserted
* string will be parsed into a ConfigNode using the same options used to
* parse the entire document, and the text will be inserted
* as-is into the document, with leading and trailing whitespace removed.
* If a concatenation is passed in for newValue but the document was parsed
* with JSON, the first value in the concatenation will be parsed and inserted
* into the ConfigDocument.
* @return a copy of the ConfigDocument with the desired value at the desired path
*/
ConfigDocument setValue(String path, String newValue);
/**
* Returns a new ConfigDocument that is a copy of the current ConfigDocument,
* but with the desired value set at the desired path as with {@link #setValue(String, String)},
* but takes a ConfigValue instead of a string.
*
* @param path the path at which to set the desired value
* @param newValue the value to set at the desired path, represented as a ConfigValue.
* The rendered text of the ConfigValue will be inserted into the
* ConfigDocument.
* @return a copy of the ConfigDocument with the desired value at the desired path
*/
ConfigDocument setValue(String path, ConfigValue newValue);
/**
* The original text of the input, modified if necessary with
* any replaced or added values.

View File

@ -16,4 +16,9 @@ abstract class AbstractConfigNode implements ConfigNode {
}
return origText.toString();
}
@Override
final public boolean equals(Object other) {
return other instanceof AbstractConfigNode && render().equals(((AbstractConfigNode)other).render());
}
}

View File

@ -644,7 +644,16 @@ final class ConfigDocumentParser {
throw parseError("Empty value");
}
if (flavor == ConfigSyntax.JSON) {
return parseValue(t);
AbstractConfigNodeValue node = parseValue(t);
t = nextToken();
while (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t)) {
t = nextToken();
}
if (t == Tokens.END) {
return node;
} else {
throw parseError("Tried to parse a concatenation. Concatenations not allowed in valid JSON");
}
} else {
putBack(t);
ArrayList<AbstractConfigNode> nodes = new ArrayList();

View File

@ -33,7 +33,7 @@ final class ConfigNodeObject extends ConfigNodeComplexValue {
if (node.value() instanceof ConfigNodeObject) {
Path remainingPath = desiredPath.subPath(key.length());
childrenCopy.set(i, node.replaceValue(((ConfigNodeObject)node.value()).changeValueOnPath(remainingPath, valueCopy)));
if (valueCopy != null && node.render() != super.children.get(i).render())
if (valueCopy != null && !node.equals(super.children.get(i)))
valueCopy = null;
}
}

View File

@ -30,6 +30,10 @@ final class SimpleConfigDocument implements ConfigDocument {
return new SimpleConfigDocument(((ConfigNodeObject)configNodeTree).setValueOnPath(path, parsedValue, parseOptions.getSyntax()), parseOptions);
}
public ConfigDocument setValue(String path, ConfigValue newValue) {
return setValue(path, newValue.render());
}
public String render() {
return configNodeTree.render();
}

View File

@ -1,76 +1,75 @@
package com.typesafe.config.impl
import com.typesafe.config.{ConfigException, ConfigSyntax, ConfigParseOptions}
import com.typesafe.config.{ ConfigException, ConfigSyntax, ConfigParseOptions }
import org.junit.Assert._
import org.junit.Test
class ConfigDocumentParserTest extends TestUtils {
private def parseTest(origText: String) {
val node = ConfigDocumentParser.parse(tokenize(origText))
assertEquals(origText, node.render())
}
private def parseTest(origText: String) {
val node = ConfigDocumentParser.parse(tokenize(origText))
assertEquals(origText, node.render())
}
private def parseJSONFailuresTest(origText: String, containsMessage: String) {
var exceptionThrown = false
try {
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains(containsMessage))
}
assertTrue(exceptionThrown)
}
private def parseJSONFailuresTest(origText: String, containsMessage: String) {
var exceptionThrown = false
try {
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains(containsMessage))
}
assertTrue(exceptionThrown)
}
private def parseSimpleValueTest(origText: String, finalText: String = null) {
val expectedRenderedText = if (finalText == null) origText else finalText
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(expectedRenderedText, node.render())
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
private def parseSimpleValueTest(origText: String, finalText: String = null) {
val expectedRenderedText = if (finalText == null) origText else finalText
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(expectedRenderedText, node.render())
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(expectedRenderedText, nodeJSON.render())
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
}
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(expectedRenderedText, nodeJSON.render())
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
}
private def parseComplexValueTest(origText: String) {
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
private def parseComplexValueTest(origText: String) {
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
assertTrue(node.isInstanceOf[AbstractConfigNodeValue])
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(origText, nodeJSON.render())
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
}
val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(origText, nodeJSON.render())
assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue])
}
private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) {
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) {
val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
var exceptionThrown = false
try {
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains(containsMessage))
}
assertTrue(exceptionThrown)
}
var exceptionThrown = false
try {
ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains(containsMessage))
}
assertTrue(exceptionThrown)
}
@Test
def parseSuccess {
parseTest("foo:bar")
parseTest(" foo : bar ")
parseTest("""include "foo.conf" """)
@Test
def parseSuccess {
parseTest("foo:bar")
parseTest(" foo : bar ")
parseTest("""include "foo.conf" """)
// Can parse a map with all simple types
parseTest(
"""
// Can parse a map with all simple types
parseTest(
"""
aUnquoted : bar
aString = "qux"
aNum:123
@ -81,15 +80,15 @@ class ConfigDocumentParserTest extends TestUtils {
aSub = ${a.b}
include "foo.conf"
""")
parseTest("{}")
parseTest("{foo:bar}")
parseTest("{ foo : bar }")
parseTest("{foo:bar} ")
parseTest("""{include "foo.conf"}""")
parseTest("{}")
parseTest("{foo:bar}")
parseTest("{ foo : bar }")
parseTest("{foo:bar} ")
parseTest("""{include "foo.conf"}""")
//Can parse a map with all simple types
parseTest(
"""{
//Can parse a map with all simple types
parseTest(
"""{
aUnquoted : bar
aString = "qux"
aNum:123
@ -101,9 +100,9 @@ class ConfigDocumentParserTest extends TestUtils {
include "foo.conf"
}""")
// Test that maps can be nested within other maps
parseTest(
"""
// Test that maps can be nested within other maps
parseTest(
"""
foo.bar.baz : {
qux : "abcdefg"
"abc".def."ghi" : 123
@ -112,34 +111,34 @@ class ConfigDocumentParserTest extends TestUtils {
qux = 123.456
""")
// Test that comments can be parsed in maps
parseTest(
"""{
// Test that comments can be parsed in maps
parseTest(
"""{
foo: bar
// This is a comment
baz:qux // This is another comment
}""")
// Basic array tests
parseTest("[]")
parseTest("[foo]")
// Basic array tests
parseTest("[]")
parseTest("[foo]")
// Test trailing comment and whitespace
parseTest("[foo,]")
parseTest("[foo,] ")
// Test trailing comment and whitespace
parseTest("[foo,]")
parseTest("[foo,] ")
// Can parse arrays with all simple types
parseTest("""[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]""")
parseTest("""[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]""")
// Can parse arrays with all simple types
parseTest("""[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]""")
parseTest("""[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]""")
// Basic concatenation tests
parseTest("[foo bar baz qux]")
parseTest("{foo: foo bar baz qux}")
parseTest("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]")
// Basic concatenation tests
parseTest("[foo bar baz qux]")
parseTest("{foo: foo bar baz qux}")
parseTest("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]")
// Complex node with all types test
parseTest(
"""{
// Complex node with all types test
parseTest(
"""{
foo: bar baz qux ernie
// The above was a concatenation
@ -172,9 +171,9 @@ class ConfigDocumentParserTest extends TestUtils {
// Did I cover everything?
}""")
// Can correctly parse a JSON string
val origText =
"""{
// Can correctly parse a JSON string
val origText =
"""{
"foo": "bar",
"baz": 123,
"qux": true,
@ -185,74 +184,82 @@ class ConfigDocumentParserTest extends TestUtils {
]
}
"""
val node = ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(origText, node.render())
}
val node = ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals(origText, node.render())
}
@Test
def parseJSONFailures() {
// JSON does not support concatenations
parseJSONFailuresTest("""{ "foo": 123 456 789 } """, "Expecting close brace } or a comma")
@Test
def parseJSONFailures() {
// JSON does not support concatenations
parseJSONFailuresTest("""{ "foo": 123 456 789 } """, "Expecting close brace } or a comma")
// JSON must begin with { or [
parseJSONFailuresTest(""""a": 123, "b": 456"""", "Document must have an object or array at root")
// JSON must begin with { or [
parseJSONFailuresTest(""""a": 123, "b": 456"""", "Document must have an object or array at root")
// JSON does not support unquoted text
parseJSONFailuresTest("""{"foo": unquotedtext}""", "Token not allowed in valid JSON")
// JSON does not support unquoted text
parseJSONFailuresTest("""{"foo": unquotedtext}""", "Token not allowed in valid JSON")
// JSON does not support substitutions
parseJSONFailuresTest("""{"foo": ${"a.b"}}""", "Substitutions (${} syntax) not allowed in JSON")
// JSON does not support substitutions
parseJSONFailuresTest("""{"foo": ${"a.b"}}""", "Substitutions (${} syntax) not allowed in JSON")
// JSON does not support multi-element paths
parseJSONFailuresTest("""{"foo"."bar": 123}""", "Token not allowed in valid JSON")
// JSON does not support multi-element paths
parseJSONFailuresTest("""{"foo"."bar": 123}""", "Token not allowed in valid JSON")
// JSON does not support =
parseJSONFailuresTest("""{"foo"=123}""", """Key '"foo"' may not be followed by token: '='""")
// JSON does not support =
parseJSONFailuresTest("""{"foo"=123}""", """Key '"foo"' may not be followed by token: '='""")
// JSON does not support +=
parseJSONFailuresTest("""{"foo" += "bar"}""", """Key '"foo"' may not be followed by token: '+='""")
// JSON does not support +=
parseJSONFailuresTest("""{"foo" += "bar"}""", """Key '"foo"' may not be followed by token: '+='""")
// JSON does not support duplicate keys
parseJSONFailuresTest("""{"foo" : 123, "foo": 456}""", "JSON does not allow duplicate fields")
// JSON does not support duplicate keys
parseJSONFailuresTest("""{"foo" : 123, "foo": 456}""", "JSON does not allow duplicate fields")
// JSON does not support trailing commas
parseJSONFailuresTest("""{"foo" : 123,}""", "expecting a field name after a comma, got a close brace } instead")
// JSON does not support trailing commas
parseJSONFailuresTest("""{"foo" : 123,}""", "expecting a field name after a comma, got a close brace } instead")
// JSON does not support empty documents
parseJSONFailuresTest("", "Empty document")
// JSON does not support empty documents
parseJSONFailuresTest("", "Empty document")
}
}
@Test
def parseSingleValues() {
// Parse simple values
parseSimpleValueTest("123")
parseSimpleValueTest("123.456")
parseSimpleValueTest(""""a string"""")
parseSimpleValueTest("true")
parseSimpleValueTest("false")
parseSimpleValueTest("null")
@Test
def parseSingleValues() {
// Parse simple values
parseSimpleValueTest("123")
parseSimpleValueTest("123.456")
parseSimpleValueTest(""""a string"""")
parseSimpleValueTest("true")
parseSimpleValueTest("false")
parseSimpleValueTest("null")
// Parse Simple Value throws out trailing and leading whitespace
parseSimpleValueTest(" 123", "123")
parseSimpleValueTest("123 ", "123")
parseSimpleValueTest(" 123 ", "123")
// Parse Simple Value throws out trailing and leading whitespace
parseSimpleValueTest(" 123", "123")
parseSimpleValueTest("123 ", "123")
parseSimpleValueTest(" 123 ", "123")
// Can parse complex values
parseComplexValueTest("""{"a": "b"}""")
parseComplexValueTest("""["a","b","c"]""")
// Can parse complex values
parseComplexValueTest("""{"a": "b"}""")
parseComplexValueTest("""["a","b","c"]""")
parseSingleValueInvalidJSONTest("unquotedtext", "Token not allowed in valid JSON")
parseSingleValueInvalidJSONTest("${a.b}", "Substitutions (${} syntax) not allowed in JSON")
parseSingleValueInvalidJSONTest("unquotedtext", "Token not allowed in valid JSON")
parseSingleValueInvalidJSONTest("${a.b}", "Substitutions (${} syntax) not allowed in JSON")
// Check that concatenations are handled by CONF parsing
var origText = "123 456 unquotedtext abc"
var node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
// Check that concatenations are handled by CONF parsing
var origText = "123 456 unquotedtext abc"
var node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults())
assertEquals(origText, node.render())
// Check that concatenations in JSON will only return the first value passed in
origText = "123 456 789"
node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
assertEquals("123", node.render())
}
// Check that concatenations in JSON will throw an error
origText = "123 456 789"
var exceptionThrown = false
try {
node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("Tried to parse a concatenation. Concatenations not allowed in valid JSON"))
}
assertTrue(exceptionThrown)
}
}

View File

@ -1,10 +1,10 @@
package com.typesafe.config.impl
import java.io.{BufferedReader, FileReader}
import java.io.{ BufferedReader, FileReader }
import java.nio.charset.StandardCharsets
import java.nio.file.{Paths, Files}
import java.nio.file.{ Paths, Files }
import com.typesafe.config.{ConfigException, ConfigSyntax, ConfigParseOptions, ConfigDocumentFactory}
import com.typesafe.config._
import org.junit.Assert._
import org.junit.Test
@ -36,7 +36,7 @@ class ConfigDocumentTest extends TestUtils {
// Can handle parsing/replacement with a complicated map
var origText =
"""{
"""{
"a":123,
"b": 123.456,
"c": true,
@ -53,7 +53,7 @@ class ConfigDocumentTest extends TestUtils {
}
}"""
var finalText =
"""{
"""{
"a":123,
"b": 123.456,
"c": true,
@ -72,10 +72,9 @@ class ConfigDocumentTest extends TestUtils {
configDocumentReplaceConfTest(origText, finalText, """"i am now a string"""", "h.b.a")
configDocumentReplaceJsonTest(origText, finalText, """"i am now a string"""", "h.b.a")
// Can handle replacing values with maps
finalText =
"""{
"""{
"a":123,
"b": 123.456,
"c": true,
@ -96,7 +95,7 @@ class ConfigDocumentTest extends TestUtils {
// Can handle replacing values with arrays
finalText =
"""{
"""{
"a":123,
"b": 123.456,
"c": true,
@ -116,7 +115,7 @@ class ConfigDocumentTest extends TestUtils {
configDocumentReplaceJsonTest(origText, finalText, "[1,2,3,4,5]", "h.b.a")
finalText =
"""{
"""{
"a":123,
"b": 123.456,
"c": true,
@ -133,7 +132,7 @@ class ConfigDocumentTest extends TestUtils {
}
}"""
configDocumentReplaceConfTest(origText, finalText,
"this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a")
"this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a")
}
@Test
@ -167,7 +166,20 @@ class ConfigDocumentTest extends TestUtils {
}
@Test
def configDocumentReplaceFailure {
def configDocumentSetNewConfigValue {
val origText = "{\"a\": \"b\"}"
val finalText = "{\"a\": 12}"
val configDocHOCON = ConfigDocumentFactory.parseString(origText)
val configDocJSON = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults.setSyntax(ConfigSyntax.JSON))
val newValue = ConfigValueFactory.fromAnyRef(12)
assertEquals(origText, configDocHOCON.render())
assertEquals(origText, configDocJSON.render())
assertEquals(finalText, configDocHOCON.setValue("a", newValue).render())
assertEquals(finalText, configDocJSON.setValue("a", newValue).render())
}
@Test
def configDocumentArrayReplaceFailure {
// Attempting a replace on a ConfigDocument parsed from an array throws an error
val origText = "[1, 2, 3, 4, 5]"
val document = ConfigDocumentFactory.parseString(origText)
@ -175,10 +187,46 @@ class ConfigDocumentTest extends TestUtils {
try {
document.setValue("a", "1")
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("ConfigDocument had an array at the root level"))
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("ConfigDocument had an array at the root level"))
}
assertTrue(exceptionThrown)
}
@Test
def configDocumentJSONReplaceFailure {
// Attempting a replace on a ConfigDocument parsed from JSON with a value using HOCON syntax
// will fail
val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}"
val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
var exceptionThrown = false
try {
document.setValue("foo", "unquoted")
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("Token not allowed in valid JSON"))
}
assertTrue(exceptionThrown)
}
@Test
def configDocumentJSONReplaceWithConcatenationFailure {
// Attempting a replace on a ConfigDocument parsed from JSON with a concatenation will
// fail
val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}"
val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON))
var exceptionThrown = false
try {
document.setValue("foo", "1 2 3 concatenation")
} catch {
case e: Exception =>
exceptionThrown = true
assertTrue(e.isInstanceOf[ConfigException])
assertTrue(e.getMessage.contains("Tried to parse a concatenation. Concatenations not allowed in valid JSON"))
}
assertTrue(exceptionThrown)
}
@ -205,4 +253,5 @@ class ConfigDocumentTest extends TestUtils {
val configDocumentFile = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf"))
assertEquals(configDocumentFile.render(), configDocument.render())
}
}

View File

@ -33,8 +33,8 @@ class ConfigNodeTest extends TestUtils {
private def topLevelValueReplaceTest(value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue, key: String = "foo") {
val complexNodeChildren = List(nodeOpenBrace,
nodeKeyValuePair(nodeWhitespace(" "), configNodeKey(key),value, nodeWhitespace(" ")),
nodeCloseBrace)
nodeKeyValuePair(nodeWhitespace(" "), configNodeKey(key), value, nodeWhitespace(" ")),
nodeCloseBrace)
val complexNode = configNodeObject(complexNodeChildren)
val newNode = complexNode.setValueOnPath(key, newValue)
val origText = "{ " + key + " : " + value.render() + " }"
@ -151,8 +151,8 @@ class ConfigNodeTest extends TestUtils {
// Ensure maps can be replaced
val nestedMap = configNodeObject(List(nodeOpenBrace, configNodeKey("abc"),
nodeColon, configNodeSimpleValue(tokenString("a string")),
nodeCloseBrace))
nodeColon, configNodeSimpleValue(tokenString("a string")),
nodeCloseBrace))
topLevelValueReplaceTest(nestedMap, nodeInt(10))
topLevelValueReplaceTest(nodeInt(10), nestedMap)
topLevelValueReplaceTest(array, nestedMap)
@ -168,7 +168,6 @@ class ConfigNodeTest extends TestUtils {
topLevelValueReplaceTest(array, concatenation)
topLevelValueReplaceTest(concatenation, array)
//Ensure a key with format "a.b" will be properly replaced
topLevelValueReplaceTest(nodeInt(10), nestedMap, "foo.bar")
}
@ -195,25 +194,25 @@ class ConfigNodeTest extends TestUtils {
def replaceNestedNodes() {
// Test that all features of node replacement in a map work in a complex map containing nested maps
val origText = "foo : bar\nbaz : {\n\t\"abc.def\" : 123\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" +
"def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}"
"def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}"
val lowestLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(6),
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)),
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)),
nodeWhitespace("\t"), nodeCloseBrace))
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)),
nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)),
nodeWhitespace("\t"), nodeCloseBrace))
val higherLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(2),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123)), nodeLine(3)),
nodeWhitespace("\t"), configNodeBasic(tokenCommentDoubleSlash("This is a comment about the below setting")),
nodeLine(4), nodeLine(5),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""),
nodeCloseBrace))
val origNode = configNodeObject(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)),
nodeKeyValuePair(configNodeKey("baz"), higherLevelMap, nodeLine(10)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)),
nodeCloseBrace))
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123)), nodeLine(3)),
nodeWhitespace("\t"), configNodeBasic(tokenCommentDoubleSlash("This is a comment about the below setting")),
nodeLine(4), nodeLine(5),
nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""),
nodeCloseBrace))
val origNode = configNodeObject(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)),
nodeKeyValuePair(configNodeKey("baz"), higherLevelMap, nodeLine(10)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)),
nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)),
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\n\"this.does.not.exist@@@+$#\" : {\nend : doesnotexist\n}\n}\n}\nbaz.abc.ghi : randomunquotedString\n}"
"def : false\n\t\n\"this.does.not.exist@@@+$#\" : {\nend : doesnotexist\n}\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