mirror of
https://github.com/lightbend/config.git
synced 2025-03-26 12:06:02 +08:00
add ConfigRenderOptions.setJson(false)
This allows you to render using HOCON extensions (other than comments; comments are still controlled by separate options). Intended to address github issue #44
This commit is contained in:
parent
5f486f65ac
commit
c38e849f43
config/src
@ -20,11 +20,14 @@ public final class ConfigRenderOptions {
|
||||
private final boolean originComments;
|
||||
private final boolean comments;
|
||||
private final boolean formatted;
|
||||
private final boolean json;
|
||||
|
||||
private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted) {
|
||||
private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted,
|
||||
boolean json) {
|
||||
this.originComments = originComments;
|
||||
this.comments = comments;
|
||||
this.formatted = formatted;
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,7 +38,7 @@ public final class ConfigRenderOptions {
|
||||
* @return the default render options
|
||||
*/
|
||||
public static ConfigRenderOptions defaults() {
|
||||
return new ConfigRenderOptions(true, true, true);
|
||||
return new ConfigRenderOptions(true, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +48,7 @@ public final class ConfigRenderOptions {
|
||||
* @return the concise render options
|
||||
*/
|
||||
public static ConfigRenderOptions concise() {
|
||||
return new ConfigRenderOptions(false, false, false);
|
||||
return new ConfigRenderOptions(false, false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +64,7 @@ public final class ConfigRenderOptions {
|
||||
if (value == comments)
|
||||
return this;
|
||||
else
|
||||
return new ConfigRenderOptions(originComments, value, formatted);
|
||||
return new ConfigRenderOptions(originComments, value, formatted, json);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,7 +82,7 @@ public final class ConfigRenderOptions {
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* {@code setOriginComments()} controls only these autogenerated
|
||||
* "origin of this setting" comments, to toggle regular comments use
|
||||
@ -94,7 +97,7 @@ public final class ConfigRenderOptions {
|
||||
if (value == originComments)
|
||||
return this;
|
||||
else
|
||||
return new ConfigRenderOptions(value, comments, formatted);
|
||||
return new ConfigRenderOptions(value, comments, formatted, json);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +122,7 @@ public final class ConfigRenderOptions {
|
||||
if (value == formatted)
|
||||
return this;
|
||||
else
|
||||
return new ConfigRenderOptions(originComments, comments, value);
|
||||
return new ConfigRenderOptions(originComments, comments, value, json);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,4 +134,49 @@ public final class ConfigRenderOptions {
|
||||
public boolean getFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options with JSON toggled. JSON means that HOCON extensions
|
||||
* (omitting commas, quotes for example) won't be used. However, whether to
|
||||
* use comments is controlled by the separate {@link #setComments(boolean)}
|
||||
* and {@link #setOriginComments(boolean)} options. So if you enable
|
||||
* comments you will get invalid JSON despite setting this to true.
|
||||
*
|
||||
* @param value
|
||||
* true to include non-JSON extensions in the render
|
||||
* @return options with requested setting for JSON
|
||||
*/
|
||||
public ConfigRenderOptions setJson(boolean value) {
|
||||
if (value == json)
|
||||
return this;
|
||||
else
|
||||
return new ConfigRenderOptions(originComments, comments, formatted, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the options enable JSON. This method is mostly used by
|
||||
* the config lib internally, not by applications.
|
||||
*
|
||||
* @return true if only JSON should be rendered
|
||||
*/
|
||||
public boolean getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("ConfigRenderOptions(");
|
||||
if (originComments)
|
||||
sb.append("originComments,");
|
||||
if (comments)
|
||||
sb.append("comments,");
|
||||
if (formatted)
|
||||
sb.append("formatted,");
|
||||
if (json)
|
||||
sb.append("json,");
|
||||
if (sb.charAt(sb.length() - 1) == ',')
|
||||
sb.setLength(sb.length() - 1);
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||
|
||||
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;
|
||||
@ -294,11 +295,28 @@ abstract class AbstractConfigValue implements ConfigValue, MergeableValue {
|
||||
|
||||
protected void render(StringBuilder sb, int indent, String atKey, ConfigRenderOptions options) {
|
||||
if (atKey != null) {
|
||||
sb.append(ConfigImplUtil.renderJsonString(atKey));
|
||||
if (options.getFormatted())
|
||||
sb.append(" : ");
|
||||
String renderedKey;
|
||||
if (options.getJson())
|
||||
renderedKey = ConfigImplUtil.renderJsonString(atKey);
|
||||
else
|
||||
sb.append(":");
|
||||
renderedKey = ConfigImplUtil.renderStringUnquotedIfPossible(atKey);
|
||||
|
||||
sb.append(renderedKey);
|
||||
|
||||
if (options.getJson()) {
|
||||
if (options.getFormatted())
|
||||
sb.append(" : ");
|
||||
else
|
||||
sb.append(":");
|
||||
} else {
|
||||
// in non-JSON we can omit the colon or equals before an object
|
||||
if (this instanceof ConfigObject) {
|
||||
if (options.getFormatted())
|
||||
sb.append(' ');
|
||||
} else {
|
||||
sb.append("=");
|
||||
}
|
||||
}
|
||||
}
|
||||
render(sb, indent, options);
|
||||
}
|
||||
|
@ -72,6 +72,30 @@ final public class ConfigImplUtil {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static String renderStringUnquotedIfPossible(String s) {
|
||||
// this can quote unnecessarily as long as it never fails to quote when
|
||||
// necessary
|
||||
if (s.length() == 0)
|
||||
return renderJsonString(s);
|
||||
|
||||
int first = s.codePointAt(0);
|
||||
if (Character.isDigit(first))
|
||||
return renderJsonString(s);
|
||||
|
||||
if (s.startsWith("include") || s.startsWith("true") || s.startsWith("false")
|
||||
|| s.startsWith("null") || s.contains("//"))
|
||||
return renderJsonString(s);
|
||||
|
||||
// only unquote if it's pure alphanumeric
|
||||
for (int i = 0; i < s.length(); ++i) {
|
||||
char c = s.charAt(i);
|
||||
if (!(Character.isLetter(c) || Character.isDigit(c)))
|
||||
return renderJsonString(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static boolean isWhitespace(int codepoint) {
|
||||
switch (codepoint) {
|
||||
// try to hit the most common ASCII ones first, then the nonbreaking
|
||||
|
@ -38,7 +38,12 @@ final class ConfigString extends AbstractConfigValue implements Serializable {
|
||||
|
||||
@Override
|
||||
protected void render(StringBuilder sb, int indent, ConfigRenderOptions options) {
|
||||
sb.append(ConfigImplUtil.renderJsonString(value));
|
||||
String rendered;
|
||||
if (options.getJson())
|
||||
rendered = ConfigImplUtil.renderJsonString(value);
|
||||
else
|
||||
rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value);
|
||||
sb.append(rendered);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -368,9 +368,15 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
||||
if (isEmpty()) {
|
||||
sb.append("{}");
|
||||
} else {
|
||||
sb.append("{");
|
||||
boolean outerBraces = indent > 0 || options.getJson();
|
||||
|
||||
if (outerBraces)
|
||||
sb.append("{");
|
||||
|
||||
if (options.getFormatted())
|
||||
sb.append('\n');
|
||||
|
||||
int separatorCount = 0;
|
||||
for (String k : keySet()) {
|
||||
AbstractConfigValue v;
|
||||
v = value.get(k);
|
||||
@ -391,18 +397,29 @@ final class SimpleConfigObject extends AbstractConfigObject implements Serializa
|
||||
}
|
||||
indent(sb, indent + 1, options);
|
||||
v.render(sb, indent + 1, k, options);
|
||||
sb.append(",");
|
||||
if (options.getFormatted())
|
||||
|
||||
if (options.getFormatted()) {
|
||||
if (options.getJson()) {
|
||||
sb.append(",");
|
||||
separatorCount = 2;
|
||||
} else {
|
||||
separatorCount = 1;
|
||||
}
|
||||
sb.append('\n');
|
||||
} else {
|
||||
sb.append(",");
|
||||
separatorCount = 1;
|
||||
}
|
||||
}
|
||||
// chop comma or newline
|
||||
sb.setLength(sb.length() - 1);
|
||||
// chop last commas/newlines
|
||||
sb.setLength(sb.length() - separatorCount);
|
||||
if (options.getFormatted()) {
|
||||
sb.setLength(sb.length() - 1); // also chop comma
|
||||
sb.append("\n"); // put a newline back
|
||||
indent(sb, indent, options);
|
||||
}
|
||||
sb.append("}");
|
||||
|
||||
if (outerBraces)
|
||||
sb.append("}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,12 @@ object RenderExample extends App {
|
||||
val formatted = args.contains("--formatted")
|
||||
val originComments = args.contains("--origin-comments")
|
||||
val comments = args.contains("--comments")
|
||||
val hocon = args.contains("--hocon")
|
||||
val options = ConfigRenderOptions.defaults()
|
||||
.setFormatted(formatted)
|
||||
.setOriginComments(originComments)
|
||||
.setComments(comments)
|
||||
.setJson(!hocon)
|
||||
|
||||
def render(what: String) {
|
||||
val conf = ConfigFactory.defaultOverrides()
|
||||
|
@ -995,11 +995,13 @@ class ConfigTest extends TestUtils {
|
||||
for (
|
||||
formatted <- allBooleans;
|
||||
originComments <- allBooleans;
|
||||
comments <- allBooleans
|
||||
comments <- allBooleans;
|
||||
json <- allBooleans
|
||||
) yield ConfigRenderOptions.defaults()
|
||||
.setFormatted(formatted)
|
||||
.setOriginComments(originComments)
|
||||
.setComments(comments)
|
||||
.setJson(json)
|
||||
} toSeq
|
||||
|
||||
for (i <- 1 to 10) {
|
||||
@ -1011,16 +1013,20 @@ class ConfigTest extends TestUtils {
|
||||
val unresolvedRender = conf.root.render(renderOptions)
|
||||
val resolved = conf.resolve()
|
||||
val resolvedRender = resolved.root.render(renderOptions)
|
||||
val unresolvedParsed = ConfigFactory.parseString(unresolvedRender, ConfigParseOptions.defaults())
|
||||
val resolvedParsed = ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults())
|
||||
try {
|
||||
assertEquals(conf.root, ConfigFactory.parseString(unresolvedRender, ConfigParseOptions.defaults()).root)
|
||||
assertEquals(resolved.root, ConfigFactory.parseString(resolvedRender, ConfigParseOptions.defaults()).root)
|
||||
assertEquals("unresolved options=" + renderOptions, conf.root, unresolvedParsed.root)
|
||||
assertEquals("resolved options=" + renderOptions, resolved.root, resolvedParsed.root)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
System.err.println("unresolvedRender = " + unresolvedRender)
|
||||
System.err.println("resolvedRender = " + resolvedRender)
|
||||
case e: Throwable =>
|
||||
System.err.println("UNRESOLVED diff:")
|
||||
showDiff(conf.root, unresolvedParsed.root)
|
||||
System.err.println("RESOLVED diff:")
|
||||
showDiff(resolved.root, resolvedParsed.root)
|
||||
throw e
|
||||
}
|
||||
if (!(renderOptions.getComments() || renderOptions.getOriginComments())) {
|
||||
if (renderOptions.getJson() && !(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));
|
||||
|
@ -50,41 +50,6 @@ class EquivalentsTest extends TestUtils {
|
||||
postParse(ConfigFactory.parseFile(f, options).root)
|
||||
}
|
||||
|
||||
private def printIndented(indent: Int, s: String): Unit = {
|
||||
for (i <- 0 to indent)
|
||||
System.err.print(' ')
|
||||
System.err.println(s)
|
||||
}
|
||||
|
||||
private def showDiff(a: ConfigValue, b: ConfigValue, indent: Int = 0): Unit = {
|
||||
if (a != b) {
|
||||
if (a.valueType != b.valueType) {
|
||||
printIndented(indent, "- " + a.valueType)
|
||||
printIndented(indent, "+ " + b.valueType)
|
||||
} else if (a.valueType == ConfigValueType.OBJECT) {
|
||||
import scala.collection.JavaConverters._
|
||||
printIndented(indent, "OBJECT")
|
||||
val aS = a.asInstanceOf[ConfigObject].asScala
|
||||
val bS = b.asInstanceOf[ConfigObject].asScala
|
||||
for (aKV <- aS) {
|
||||
val bVOption = bS.get(aKV._1)
|
||||
if (Some(aKV._2) != bVOption) {
|
||||
printIndented(indent + 1, aKV._1)
|
||||
if (bVOption.isDefined) {
|
||||
showDiff(aKV._2, bVOption.get, indent + 2)
|
||||
} else {
|
||||
printIndented(indent + 2, "- " + aKV._2)
|
||||
printIndented(indent + 2, "+ (missing)")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printIndented(indent, "- " + a)
|
||||
printIndented(indent, "+ " + b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// would like each "equivNN" directory to be a suite and each file in the dir
|
||||
// to be a test, but not sure how to convince junit to do that.
|
||||
@Test
|
||||
|
@ -549,7 +549,7 @@ class PublicApiTest extends TestUtils {
|
||||
|
||||
@Test
|
||||
def quoteString() {
|
||||
// the actual quote logic shoudl be tested OK in the non-public-API tests,
|
||||
// the actual quote logic should be tested OK in the non-public-API tests,
|
||||
// this is just to test the public wrapper.
|
||||
|
||||
assertEquals("\"\"", ConfigUtil.quoteString(""))
|
||||
|
@ -685,4 +685,40 @@ abstract trait TestUtils {
|
||||
})
|
||||
f.get
|
||||
}
|
||||
|
||||
private def printIndented(indent: Int, s: String): Unit = {
|
||||
for (i <- 0 to indent)
|
||||
System.err.print(' ')
|
||||
System.err.println(s)
|
||||
}
|
||||
|
||||
protected def showDiff(a: ConfigValue, b: ConfigValue, indent: Int = 0): Unit = {
|
||||
if (a != b) {
|
||||
if (a.valueType != b.valueType) {
|
||||
printIndented(indent, "- " + a.valueType)
|
||||
printIndented(indent, "+ " + b.valueType)
|
||||
} else if (a.valueType == ConfigValueType.OBJECT) {
|
||||
import scala.collection.JavaConverters._
|
||||
printIndented(indent, "OBJECT")
|
||||
val aS = a.asInstanceOf[ConfigObject].asScala
|
||||
val bS = b.asInstanceOf[ConfigObject].asScala
|
||||
for (aKV <- aS) {
|
||||
val bVOption = bS.get(aKV._1)
|
||||
if (Some(aKV._2) != bVOption) {
|
||||
printIndented(indent + 1, aKV._1)
|
||||
if (bVOption.isDefined) {
|
||||
showDiff(aKV._2, bVOption.get, indent + 2)
|
||||
} else {
|
||||
printIndented(indent + 2, "- " + aKV._2)
|
||||
printIndented(indent + 2, "+ (missing)")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printIndented(indent, "- " + a)
|
||||
printIndented(indent, "+ " + b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,4 +56,38 @@ class UtilTest extends TestUtils {
|
||||
assertFalse(ConfigImplUtil.equalsHandlingNull(null, new Object()))
|
||||
assertTrue(ConfigImplUtil.equalsHandlingNull("", ""))
|
||||
}
|
||||
|
||||
val lotsOfStrings = (invalidJson ++ validConf).map(_.test)
|
||||
|
||||
private def roundtripJson(s: String) {
|
||||
val rendered = ConfigImplUtil.renderJsonString(s)
|
||||
val parsed = parseConfig("{ foo: " + rendered + "}").getString("foo")
|
||||
assertTrue("String round-tripped through maybe-unquoted escaping '" + s + "' " + s.length +
|
||||
" rendering '" + rendered + "' " + rendered.length +
|
||||
" parsed '" + parsed + "' " + parsed.length,
|
||||
s == parsed)
|
||||
}
|
||||
|
||||
private def roundtripUnquoted(s: String) {
|
||||
val rendered = ConfigImplUtil.renderStringUnquotedIfPossible(s)
|
||||
val parsed = parseConfig("{ foo: " + rendered + "}").getString("foo")
|
||||
assertTrue("String round-tripped through maybe-unquoted escaping '" + s + "' " + s.length +
|
||||
" rendering '" + rendered + "' " + rendered.length +
|
||||
" parsed '" + parsed + "' " + parsed.length,
|
||||
s == parsed)
|
||||
}
|
||||
|
||||
@Test
|
||||
def renderJsonString() {
|
||||
for (s <- lotsOfStrings) {
|
||||
roundtripJson(s)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
def renderUnquotedIfPossible() {
|
||||
for (s <- lotsOfStrings) {
|
||||
roundtripUnquoted(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user