From 387e106856f158ab78c57171555db90e12477df5 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Thu, 12 Apr 2012 21:34:59 -0400 Subject: [PATCH] Add ConfigRenderOptions and ConfigValue#render(options) This mostly lets you choose whether you want whitespace and comments, and somewhat as a side effect, you can get plain JSON by turning off comments. --- .../typesafe/config/ConfigRenderOptions.java | 134 ++++++++++++++++++ .../java/com/typesafe/config/ConfigValue.java | 38 ++++- .../config/impl/AbstractConfigObject.java | 3 +- .../config/impl/AbstractConfigValue.java | 34 +++-- .../config/impl/ConfigConcatenation.java | 5 +- .../config/impl/ConfigDelayedMerge.java | 37 +++-- .../config/impl/ConfigDelayedMergeObject.java | 9 +- .../com/typesafe/config/impl/ConfigNull.java | 3 +- .../typesafe/config/impl/ConfigReference.java | 3 +- .../typesafe/config/impl/ConfigString.java | 3 +- .../config/impl/SimpleConfigList.java | 26 ++-- .../config/impl/SimpleConfigObject.java | 23 +-- config/src/test/scala/Rendering.scala | 13 +- .../config/impl/ConcatenationTest.scala | 6 +- .../com/typesafe/config/impl/ConfigTest.scala | 48 +++++-- 15 files changed, 306 insertions(+), 79 deletions(-) create mode 100644 config/src/main/java/com/typesafe/config/ConfigRenderOptions.java diff --git a/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java b/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java new file mode 100644 index 00000000..c296e976 --- /dev/null +++ b/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java @@ -0,0 +1,134 @@ +/** + * Copyright (C) 2011-2012 Typesafe Inc. + */ +package com.typesafe.config; + +/** + *

+ * A set of options related to rendering a {@link ConfigValue}. Passed to + * {@link ConfigValue#render(ConfigRenderOptions)}. + * + *

+ * Here is an example of creating a {@code ConfigRenderOptions}: + * + *

+ *     ConfigRenderOptions options =
+ *         ConfigRenderOptions.defaults().setComments(false)
+ * 
+ */ +public final class ConfigRenderOptions { + private final boolean originComments; + private final boolean comments; + private final boolean formatted; + + private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted) { + this.originComments = originComments; + this.comments = comments; + this.formatted = formatted; + } + + /** + * Returns the default render options which are verbose (commented and + * formatted). See {@link ConfigRenderOptions#concise} for stripped-down + * options. This rendering will not be valid JSON since it has comments. + * + * @return the default render options + */ + public static ConfigRenderOptions defaults() { + return new ConfigRenderOptions(true, true, true); + } + + /** + * Returns concise render options (no whitespace or comments). For a + * resolved {@link Config}, the concise rendering will be valid JSON. + * + * @return the concise render options + */ + public static ConfigRenderOptions concise() { + return new ConfigRenderOptions(false, false, false); + } + + /** + * Returns options with comments toggled. This controls human-written + * comments but not the autogenerated "origin of this setting" comments, + * which are controlled by {@link ConfigRenderOptions#setOriginComments}. + * + * @param value + * true to include comments in the render + * @return options with requested setting for comments + */ + public ConfigRenderOptions setComments(boolean value) { + if (value == comments) + return this; + else + return new ConfigRenderOptions(originComments, value, formatted); + } + + /** + * Returns whether the options enable comments. This method is mostly used + * by the config lib internally, not by applications. + * + * @return true if comments should be rendered + */ + public boolean getComments() { + return comments; + } + + /** + * Returns options with origin comments toggled. If this is enabled, the + * library generates comments for each setting based on the + * {@link ConfigValue#origin} of that setting's value. For example these + * comments might tell you which file a setting comes from. + * + *

+ * {@code setOriginComments()} controls only these autogenerated + * "origin of this setting" comments, to toggle regular comments use + * {@link ConfigRenderOptions#setComments}. + * + * @param value + * true to include autogenerated setting-origin comments in the + * render + * @return options with origin comments toggled + */ + public ConfigRenderOptions setOriginComments(boolean value) { + if (value == originComments) + return this; + else + return new ConfigRenderOptions(value, comments, formatted); + } + + /** + * Returns whether the options enable automated origin comments. This method + * is mostly used by the config lib internally, not by applications. + * + * @return true if origin comments should be rendered + */ + public boolean getOriginComments() { + return originComments; + } + + /** + * Returns options with formatting toggled. Formatting means indentation and + * whitespace, enabling formatting makes things prettier but larger. + * + * @param value + * true to include comments in the render + * @return options with requested setting for formatting + */ + public ConfigRenderOptions setFormatted(boolean value) { + if (value == formatted) + return this; + else + return new ConfigRenderOptions(originComments, comments, value); + } + + /** + * Returns whether the options enable formatting. This method is mostly used + * by the config lib internally, not by applications. + * + * @return true if comments should be rendered + */ + public boolean getFormatted() { + return formatted; + } +} diff --git a/config/src/main/java/com/typesafe/config/ConfigValue.java b/config/src/main/java/com/typesafe/config/ConfigValue.java index 1f389be0..e0f7bef2 100644 --- a/config/src/main/java/com/typesafe/config/ConfigValue.java +++ b/config/src/main/java/com/typesafe/config/ConfigValue.java @@ -46,15 +46,43 @@ public interface ConfigValue extends ConfigMergeable { /** * Renders the config value as a HOCON string. This method is primarily * intended for debugging, so it tries to add helpful comments and - * whitespace. If the config value has not been resolved (see - * {@link Config#resolve}), it's possible that it can't be rendered as valid - * HOCON. In that case the rendering should still be useful for debugging - * but you might not be able to parse it. - * + * whitespace. + * + *

+ * If the config value has not been resolved (see {@link Config#resolve}), + * it's possible that it can't be rendered as valid HOCON. In that case the + * rendering should still be useful for debugging but you might not be able + * to parse it. + * + *

+ * This method is equivalent to + * {@code render(ConfigRenderOptions.defaults())}. + * * @return the rendered value */ String render(); + /** + * Renders the config value to a string, using the provided options. + * + *

+ * If the config value has not been resolved (see {@link Config#resolve}), + * it's possible that it can't be rendered as valid HOCON. In that case the + * rendering should still be useful for debugging but you might not be able + * to parse it. + * + *

+ * If the config value has been resolved and the options disable all + * HOCON-specific features (such as comments), the rendering will be valid + * JSON. If you enable HOCON-only features such as comments, the rendering + * will not be valid JSON. + * + * @param options + * the rendering options + * @return the rendered value + */ + String render(ConfigRenderOptions options); + @Override ConfigValue withFallback(ConfigMergeable other); } diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java index 5cda4f2d..ba44075f 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigObject.java @@ -13,6 +13,7 @@ import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -212,7 +213,7 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements Confi public abstract AbstractConfigValue get(Object key); @Override - protected abstract void render(StringBuilder sb, int indent, boolean formatted); + protected abstract void render(StringBuilder sb, int indent, ConfigRenderOptions options); private static UnsupportedOperationException weAreImmutable(String method) { return new UnsupportedOperationException("ConfigObject is immutable, you can't call Map." diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 1df26383..4b31808c 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -11,6 +11,7 @@ import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; /** @@ -276,36 +277,45 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue { @Override public final String toString() { StringBuilder sb = new StringBuilder(); - render(sb, 0, null /* atKey */, false /* formatted */); + render(sb, 0, null /* atKey */, ConfigRenderOptions.concise()); return getClass().getSimpleName() + "(" + sb.toString() + ")"; } - protected static void indent(StringBuilder sb, int indent) { - int remaining = indent; - while (remaining > 0) { - sb.append(" "); - --remaining; + protected static void indent(StringBuilder sb, int indent, ConfigRenderOptions options) { + if (options.getFormatted()) { + int remaining = indent; + while (remaining > 0) { + sb.append(" "); + --remaining; + } } } - protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { + protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { if (atKey != null) { sb.append(ConfigImplUtil.renderJsonString(atKey)); - sb.append(" : "); + if (options.getFormatted()) + sb.append(" : "); + else + sb.append(":"); } - render(sb, indent, formatted); + render(sb, indent, options); } - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { Object u = unwrapped(); sb.append(u.toString()); } - @Override public final String render() { + return render(ConfigRenderOptions.defaults()); + } + + @Override + public final String render(ConfigRenderOptions options) { StringBuilder sb = new StringBuilder(); - render(sb, 0, null, true /* formatted */); + render(sb, 0, null, options); return sb.toString(); } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java index 45d90085..bde6fb64 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigConcatenation.java @@ -8,6 +8,7 @@ import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** @@ -222,9 +223,9 @@ final class ConfigConcatenation extends AbstractConfigValue implements Unmergeab } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { for (AbstractConfigValue p : pieces) { - p.render(sb, indent, formatted); + p.render(sb, indent, options); } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java index 7d739d62..21f5371b 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMerge.java @@ -10,6 +10,7 @@ import java.util.List; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** @@ -215,18 +216,20 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl } @Override - protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { - render(stack, sb, indent, atKey, formatted); + protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { + render(stack, sb, indent, atKey, options); } // static method also used by ConfigDelayedMergeObject. static void render(List stack, StringBuilder sb, int indent, String atKey, - boolean formatted) { - if (formatted) { + ConfigRenderOptions options) { + boolean commentMerge = options.getComments(); + if (commentMerge) { sb.append("# unresolved merge of " + stack.size() + " values follows (\n"); if (atKey == null) { - indent(sb, indent); + indent(sb, indent, options); sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n"); + indent(sb, indent, options); sb.append("# the HOCON format has no way to list multiple root objects in a single file\n"); } } @@ -237,8 +240,8 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl int i = 0; for (AbstractConfigValue v : reversed) { - if (formatted) { - indent(sb, indent); + if (commentMerge) { + indent(sb, indent, options); if (atKey != null) { sb.append("# unmerged value " + i + " for key " + ConfigImplUtil.renderJsonString(atKey) + " from "); @@ -248,30 +251,36 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements Unmergeabl i += 1; sb.append(v.origin().description()); sb.append("\n"); + for (String comment : v.origin().comments()) { - indent(sb, indent); + indent(sb, indent, options); sb.append("# "); sb.append(comment); sb.append("\n"); } - indent(sb, indent); } + indent(sb, indent, options); if (atKey != null) { sb.append(ConfigImplUtil.renderJsonString(atKey)); - sb.append(" : "); + if (options.getFormatted()) + sb.append(" : "); + else + sb.append(":"); } - v.render(sb, indent, formatted); + v.render(sb, indent, options); sb.append(","); - if (formatted) + if (options.getFormatted()) sb.append('\n'); } // chop comma or newline sb.setLength(sb.length() - 1); - if (formatted) { + if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append("\n"); // put a newline back - indent(sb, indent); + } + if (commentMerge) { + indent(sb, indent, options); sb.append("# ) end of unresolved merge\n"); } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java index 38ccb2ef..1eb87c50 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDelayedMergeObject.java @@ -13,6 +13,7 @@ import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigMergeable; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; // This is just like ConfigDelayedMerge except we know statically @@ -168,13 +169,13 @@ final class ConfigDelayedMergeObject extends AbstractConfigObject implements Unm } @Override - protected void render(StringBuilder sb, int indent, String atKey, boolean formatted) { - ConfigDelayedMerge.render(stack, sb, indent, atKey, formatted); + protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) { + ConfigDelayedMerge.render(stack, sb, indent, atKey, options); } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { - render(sb, indent, null, formatted); + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { + render(sb, indent, null, options); } private static ConfigException notResolved() { diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNull.java b/config/src/main/java/com/typesafe/config/impl/ConfigNull.java index 927c7f44..57761810 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNull.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNull.java @@ -7,6 +7,7 @@ import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** @@ -41,7 +42,7 @@ final class ConfigNull extends AbstractConfigValue implements Serializable { } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { sb.append("null"); } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java index e498c65e..880c0ef1 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigReference.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigReference.java @@ -5,6 +5,7 @@ import java.util.Collections; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; /** @@ -126,7 +127,7 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable { } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { sb.append(expr.toString()); } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigString.java b/config/src/main/java/com/typesafe/config/impl/ConfigString.java index 5dd11001..850f29e6 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigString.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigString.java @@ -7,6 +7,7 @@ import java.io.ObjectStreamException; import java.io.Serializable; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValueType; final class ConfigString extends AbstractConfigValue implements Serializable { @@ -36,7 +37,7 @@ final class ConfigString extends AbstractConfigValue implements Serializable { } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { sb.append(ConfigImplUtil.renderJsonString(value)); } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java index 15a0330b..dcb9fa61 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigList.java @@ -14,6 +14,7 @@ import java.util.ListIterator; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigList; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; import com.typesafe.config.ConfigValueType; @@ -166,39 +167,40 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList, } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { if (value.isEmpty()) { sb.append("[]"); } else { sb.append("["); - if (formatted) + if (options.getFormatted()) sb.append('\n'); for (AbstractConfigValue v : value) { - if (formatted) { - indent(sb, indent + 1); + if (options.getOriginComments()) { + indent(sb, indent + 1, options); sb.append("# "); sb.append(v.origin().description()); sb.append("\n"); - + } + if (options.getComments()) { for (String comment : v.origin().comments()) { - indent(sb, indent + 1); + indent(sb, indent + 1, options); sb.append("# "); sb.append(comment); sb.append("\n"); } - - indent(sb, indent + 1); } - v.render(sb, indent + 1, formatted); + indent(sb, indent + 1, options); + + v.render(sb, indent + 1, options); sb.append(","); - if (formatted) + if (options.getFormatted()) sb.append('\n'); } sb.setLength(sb.length() - 1); // chop or newline - if (formatted) { + if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append('\n'); - indent(sb, indent); + indent(sb, indent, options); } sb.append("]"); } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java index 13e7dc76..1eee5de5 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigObject.java @@ -18,6 +18,7 @@ import java.util.Set; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigOrigin; +import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigValue; final class SimpleConfigObject extends AbstractConfigObject implements Serializable { @@ -324,41 +325,43 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa } @Override - protected void render(StringBuilder sb, int indent, boolean formatted) { + protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) { if (isEmpty()) { sb.append("{}"); } else { sb.append("{"); - if (formatted) + if (options.getFormatted()) sb.append('\n'); for (String k : keySet()) { AbstractConfigValue v; v = value.get(k); - if (formatted) { - indent(sb, indent + 1); + if (options.getOriginComments()) { + indent(sb, indent + 1, options); sb.append("# "); sb.append(v.origin().description()); sb.append("\n"); + } + if (options.getComments()) { for (String comment : v.origin().comments()) { - indent(sb, indent + 1); + indent(sb, indent + 1, options); sb.append("# "); sb.append(comment); sb.append("\n"); } - indent(sb, indent + 1); } - v.render(sb, indent + 1, k, formatted); + indent(sb, indent + 1, options); + v.render(sb, indent + 1, k, options); sb.append(","); - if (formatted) + if (options.getFormatted()) sb.append('\n'); } // chop comma or newline sb.setLength(sb.length() - 1); - if (formatted) { + if (options.getFormatted()) { sb.setLength(sb.length() - 1); // also chop comma sb.append("\n"); // put a newline back - indent(sb, indent); + indent(sb, indent, options); } sb.append("}"); } diff --git a/config/src/test/scala/Rendering.scala b/config/src/test/scala/Rendering.scala index 316a1cdf..92c0006b 100644 --- a/config/src/test/scala/Rendering.scala +++ b/config/src/test/scala/Rendering.scala @@ -1,17 +1,26 @@ import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions object RenderExample extends App { + val formatted = args.contains("--formatted") + val originComments = args.contains("--origin-comments") + val comments = args.contains("--comments") + val options = ConfigRenderOptions.defaults() + .setFormatted(formatted) + .setOriginComments(originComments) + .setComments(comments) + def render(what: String) { val conf = ConfigFactory.defaultOverrides() .withFallback(ConfigFactory.parseResourcesAnySyntax(classOf[ConfigFactory], "/" + what)) .withFallback(ConfigFactory.defaultReference()) println("=== BEGIN UNRESOLVED " + what) - println(conf.root.render()) + println(conf.root.render(options)) println("=== END UNRESOLVED " + what) println("=== BEGIN RESOLVED " + what) - println(conf.resolve().root.render()) + println(conf.resolve().root.render(options)) println("=== END RESOLVED " + what) println("=== BEGIN UNRESOLVED toString() " + what) diff --git a/config/src/test/scala/com/typesafe/config/impl/ConcatenationTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConcatenationTest.scala index 1e2bd03e..c2a3fc70 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConcatenationTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConcatenationTest.scala @@ -51,7 +51,7 @@ class ConcatenationTest extends TestUtils { assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("abc") && - e.getMessage.contains("""{"x" : "y"}""")) + e.getMessage.contains("""{"x":"y"}""")) } @Test @@ -62,7 +62,7 @@ class ConcatenationTest extends TestUtils { assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && e.getMessage.contains("null") && - e.getMessage.contains("""{"x" : "y"}""")) + e.getMessage.contains("""{"x":"y"}""")) } @Test @@ -293,7 +293,7 @@ class ConcatenationTest extends TestUtils { } assertTrue("wrong exception: " + e.getMessage, e.getMessage.contains("Cannot concatenate") && - e.getMessage.contains("\"x\" : \"y\"") && + e.getMessage.contains("\"x\":\"y\"") && e.getMessage.contains("[2]")) } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index 170ee7e4..11aeb032 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -16,6 +16,8 @@ import java.io.File import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigMergeable +import com.typesafe.config.ConfigRenderOptions +import com.typesafe.config.ConfigSyntax class ConfigTest extends TestUtils { @@ -989,22 +991,46 @@ class ConfigTest extends TestUtils { @Test def renderRoundTrip() { + val allBooleans = true :: false :: Nil + val optionsCombos = { + for ( + formatted <- allBooleans; + originComments <- allBooleans; + comments <- allBooleans + ) yield ConfigRenderOptions.defaults() + .setFormatted(formatted) + .setOriginComments(originComments) + .setComments(comments) + } toSeq + for (i <- 1 to 10) { val numString = i.toString val name = "/test" + { if (numString.size == 1) "0" else "" } + numString val conf = ConfigFactory.parseResourcesAnySyntax(classOf[ConfigTest], name, ConfigParseOptions.defaults().setAllowMissing(false)) - val unresolvedRender = conf.root.render() - val resolved = conf.resolve() - val resolvedRender = resolved.root.render() - try { - assertEquals(conf.root, ConfigFactory.parseString(unresolvedRender, ConfigParseOptions.defaults()).root) - assertEquals(resolved.root, ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults()).root) - } catch { - case e: Throwable => - System.err.println("unresolvedRender = " + unresolvedRender) - System.err.println("resolvedRender = " + resolvedRender) - throw e + for (renderOptions <- optionsCombos) { + val unresolvedRender = conf.root.render(renderOptions) + val resolved = conf.resolve() + val resolvedRender = resolved.root.render(renderOptions) + try { + assertEquals(conf.root, ConfigFactory.parseString(unresolvedRender, ConfigParseOptions.defaults()).root) + assertEquals(resolved.root, ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults()).root) + } catch { + case e: Exception => + System.err.println("unresolvedRender = " + unresolvedRender) + System.err.println("resolvedRender = " + resolvedRender) + throw e + } + if (!(renderOptions.getComments() || renderOptions.getOriginComments())) { + // should get valid JSON if we don't have comments and are resolved + val json = try { + ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)); + } catch { + case e: Exception => + System.err.println("resolvedRender is not valid json: " + resolvedRender) + throw e + } + } } } }