mirror of
https://github.com/lightbend/config.git
synced 2025-03-25 16:50:40 +08:00
add ConfigValue.render() which returns a String version of the value
also toString() includes the render() plus the class name.
This commit is contained in:
parent
7c55d91149
commit
69b5a765df
@ -43,6 +43,18 @@ public interface ConfigValue extends ConfigMergeable {
|
||||
*/
|
||||
Object unwrapped();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the rendered value
|
||||
*/
|
||||
String render();
|
||||
|
||||
@Override
|
||||
ConfigValue withFallback(ConfigMergeable other);
|
||||
}
|
||||
|
@ -261,20 +261,24 @@ abstract class AbstractConfigObject extends AbstractConfigValue implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(valueType().name());
|
||||
sb.append("(");
|
||||
for (String k : keySet()) {
|
||||
sb.append(k);
|
||||
sb.append("->");
|
||||
sb.append(peek(k).toString());
|
||||
sb.append(",");
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
if (isEmpty()) {
|
||||
sb.append("{}");
|
||||
} else {
|
||||
sb.append("{\n");
|
||||
for (String k : keySet()) {
|
||||
AbstractConfigValue v = peek(k);
|
||||
indent(sb, indent + 1);
|
||||
sb.append("# ");
|
||||
sb.append(v.origin().description());
|
||||
sb.append("\n");
|
||||
indent(sb, indent + 1);
|
||||
v.render(sb, indent + 1, k);
|
||||
sb.append(",\n");
|
||||
}
|
||||
indent(sb, indent);
|
||||
sb.append("}");
|
||||
}
|
||||
if (!keySet().isEmpty())
|
||||
sb.setLength(sb.length() - 1); // chop comma
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean mapEquals(Map<String, ConfigValue> a,
|
||||
|
@ -162,8 +162,37 @@ abstract class AbstractConfigValue implements ConfigValue {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return valueType().name() + "(" + unwrapped() + ")";
|
||||
public final String toString() {
|
||||
return getClass().getSimpleName() + "(" + render() + ")";
|
||||
}
|
||||
|
||||
protected static void indent(StringBuilder sb, int indent) {
|
||||
int remaining = indent;
|
||||
while (remaining > 0) {
|
||||
sb.append(" ");
|
||||
--remaining;
|
||||
}
|
||||
}
|
||||
|
||||
protected void render(StringBuilder sb, int indent, String atKey) {
|
||||
if (atKey != null) {
|
||||
sb.append(ConfigUtil.renderJsonString(atKey));
|
||||
sb.append(" : ");
|
||||
}
|
||||
render(sb, indent);
|
||||
}
|
||||
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
Object u = unwrapped();
|
||||
sb.append(u.toString());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String render() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
render(sb, 0, null);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// toString() is a debugging-oriented string but this is defined
|
||||
|
@ -5,6 +5,7 @@ package com.typesafe.config.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.typesafe.config.ConfigException;
|
||||
@ -160,16 +161,46 @@ final class ConfigDelayedMerge extends AbstractConfigValue implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DELAYED_MERGE");
|
||||
sb.append("(");
|
||||
for (Object s : stack) {
|
||||
sb.append(s.toString());
|
||||
sb.append(",");
|
||||
protected void render(StringBuilder sb, int indent, String atKey) {
|
||||
render(stack, sb, indent, atKey);
|
||||
}
|
||||
|
||||
// static method also used by ConfigDelayedMergeObject.
|
||||
static void render(List<AbstractConfigValue> stack, StringBuilder sb, int indent, String atKey) {
|
||||
sb.append("# unresolved merge of " + stack.size() + " values follows (\n");
|
||||
if (atKey == null) {
|
||||
indent(sb, indent);
|
||||
sb.append("# this unresolved merge will not be parseable because it's at the root of the object\n");
|
||||
sb.append("# the HOCON format has no way to list multiple root objects in a single file\n");
|
||||
}
|
||||
sb.setLength(sb.length() - 1); // chop comma
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
|
||||
List<AbstractConfigValue> reversed = new ArrayList<AbstractConfigValue>();
|
||||
reversed.addAll(stack);
|
||||
Collections.reverse(reversed);
|
||||
|
||||
int i = 0;
|
||||
for (AbstractConfigValue v : reversed) {
|
||||
indent(sb, indent);
|
||||
if (atKey != null) {
|
||||
sb.append("# unmerged value " + i + " for key "
|
||||
+ ConfigUtil.renderJsonString(atKey) + " from ");
|
||||
} else {
|
||||
sb.append("# unmerged value " + i + " from ");
|
||||
}
|
||||
i += 1;
|
||||
sb.append(v.origin().description());
|
||||
sb.append("\n");
|
||||
indent(sb, indent);
|
||||
if (atKey != null) {
|
||||
sb.append(ConfigUtil.renderJsonString(atKey));
|
||||
sb.append(" : ");
|
||||
}
|
||||
v.render(sb, indent);
|
||||
sb.append(",\n");
|
||||
}
|
||||
sb.setLength(sb.length() - 2); // chop comma and newline
|
||||
sb.append("\n");
|
||||
indent(sb, indent);
|
||||
sb.append("# ) end of unresolved merge\n");
|
||||
}
|
||||
}
|
||||
|
@ -139,17 +139,8 @@ class ConfigDelayedMergeObject extends AbstractConfigObject implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("DELAYED_MERGE_OBJECT");
|
||||
sb.append("(");
|
||||
for (Object s : stack) {
|
||||
sb.append(s.toString());
|
||||
sb.append(",");
|
||||
}
|
||||
sb.setLength(sb.length() - 1); // chop comma
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
protected void render(StringBuilder sb, int indent, String atKey) {
|
||||
ConfigDelayedMerge.render(stack, sb, indent, atKey);
|
||||
}
|
||||
|
||||
private static ConfigException notResolved() {
|
||||
|
@ -34,4 +34,9 @@ final class ConfigNull extends AbstractConfigValue {
|
||||
String transformToString() {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
sb.append("null");
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +29,9 @@ final class ConfigString extends AbstractConfigValue {
|
||||
String transformToString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
sb.append(ConfigUtil.renderJsonString(value));
|
||||
}
|
||||
}
|
||||
|
@ -244,16 +244,15 @@ final class ConfigSubstitution extends AbstractConfigValue implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("SUBST");
|
||||
sb.append("(");
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
for (Object p : pieces) {
|
||||
sb.append(p.toString());
|
||||
sb.append(",");
|
||||
if (p instanceof Path) {
|
||||
sb.append("${");
|
||||
sb.append(((Path) p).render());
|
||||
sb.append("}");
|
||||
} else {
|
||||
sb.append(ConfigUtil.renderJsonString((String) p));
|
||||
}
|
||||
}
|
||||
sb.setLength(sb.length() - 1); // chop comma
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -135,18 +135,26 @@ final class SimpleConfigList extends AbstractConfigValue implements ConfigList {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(valueType().name());
|
||||
sb.append("(");
|
||||
for (ConfigValue e : value) {
|
||||
sb.append(e.toString());
|
||||
sb.append(",");
|
||||
protected void render(StringBuilder sb, int indent) {
|
||||
if (value.isEmpty()) {
|
||||
sb.append("[]");
|
||||
} else {
|
||||
sb.append("[\n");
|
||||
for (AbstractConfigValue v : value) {
|
||||
indent(sb, indent + 1);
|
||||
sb.append("# ");
|
||||
sb.append(v.origin().description());
|
||||
sb.append("\n");
|
||||
indent(sb, indent + 1);
|
||||
v.render(sb, indent + 1);
|
||||
sb.append(",\n");
|
||||
}
|
||||
sb.setLength(sb.length() - 2); // chop comma and newline
|
||||
sb.append("\n");
|
||||
|
||||
indent(sb, indent);
|
||||
sb.append("]");
|
||||
}
|
||||
if (!value.isEmpty())
|
||||
sb.setLength(sb.length() - 1); // chop comma
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
14
src/test/resources/test06.conf
Normal file
14
src/test/resources/test06.conf
Normal file
@ -0,0 +1,14 @@
|
||||
## This file tests ConfigDelayedMerge and ConfigDelayedMergeObject
|
||||
|
||||
a=1
|
||||
b=2
|
||||
c=3
|
||||
d={ "foo" : "bar" }
|
||||
|
||||
# merge some x's
|
||||
x=${a}
|
||||
x=${b}
|
||||
|
||||
# merge some y's where we know one is an object
|
||||
y=${d}
|
||||
y={ "hello" : "world", "foo" : 10 }
|
18
src/test/scala/Rendering.scala
Normal file
18
src/test/scala/Rendering.scala
Normal file
@ -0,0 +1,18 @@
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
object RenderExample extends App {
|
||||
def render(what: String) {
|
||||
val conf = ConfigFactory.loadWithoutResolving(what)
|
||||
|
||||
println("=== BEGIN UNRESOLVED " + what)
|
||||
println(conf.root.render())
|
||||
println("=== END UNRESOLVED " + what)
|
||||
|
||||
println("=== BEGIN RESOLVED " + what)
|
||||
println(conf.resolve().root.render())
|
||||
println("=== END RESOLVED " + what)
|
||||
}
|
||||
|
||||
render("test01")
|
||||
render("test06")
|
||||
}
|
@ -57,6 +57,12 @@ class ConfParserTest extends TestUtils {
|
||||
val ourAST = addOffendingJsonToException("config-conf", valid.test) {
|
||||
parse(valid.test)
|
||||
}
|
||||
// let's also check round-trip rendering
|
||||
val rendered = ourAST.render()
|
||||
val reparsed = addOffendingJsonToException("config-conf-reparsed", rendered) {
|
||||
parse(rendered)
|
||||
}
|
||||
assertEquals(ourAST, reparsed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -866,4 +866,33 @@ class ConfigTest extends TestUtils {
|
||||
assertEquals("prod", conf.getString("%prod.application.mode"))
|
||||
assertEquals("Yet another blog", conf.getString("blog.title"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def test06Merge() {
|
||||
// test06 mostly exists because its render() round trip is tricky
|
||||
val conf = ConfigFactory.load("test06")
|
||||
|
||||
assertEquals(2, conf.getInt("x"))
|
||||
assertEquals(10, conf.getInt("y.foo"))
|
||||
assertEquals("world", conf.getString("y.hello"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def renderRoundTrip() {
|
||||
for (i <- 1 to 6) {
|
||||
val conf = ConfigFactory.loadWithoutResolving("test0" + i)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user