mirror of
https://github.com/lightbend/config.git
synced 2025-03-22 23:30:27 +08:00
Merge c1e4b9b3df
into d0021d1d3b
This commit is contained in:
commit
76cb93efd7
@ -27,6 +27,8 @@ crossPaths := false
|
|||||||
libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test"
|
libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test"
|
||||||
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
|
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"
|
||||||
|
|
||||||
|
libraryDependencies += "org.yaml" % "snakeyaml" % "1.12"
|
||||||
|
|
||||||
externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/"
|
externalResolvers += "Scala Tools Snapshots" at "http://scala-tools.org/repo-snapshots/"
|
||||||
|
|
||||||
checkstyleConfigLocation := CheckstyleConfigLocation.File((baseDirectory.value / "checkstyle-config.xml").toString)
|
checkstyleConfigLocation := CheckstyleConfigLocation.File((baseDirectory.value / "checkstyle-config.xml").toString)
|
||||||
|
@ -32,5 +32,10 @@ public enum ConfigSyntax {
|
|||||||
* >Java properties</a> format. Associated with the <code>.properties</code>
|
* >Java properties</a> format. Associated with the <code>.properties</code>
|
||||||
* file extension and <code>text/x-java-properties</code> Content-Type.
|
* file extension and <code>text/x-java-properties</code> Content-Type.
|
||||||
*/
|
*/
|
||||||
PROPERTIES;
|
PROPERTIES,
|
||||||
|
/**
|
||||||
|
* Standard <a href="http://www.yaml.org/spec/1.2/spec.html"> YAML 1.2 </a> format. Associated with the
|
||||||
|
* <code>.yml</code> file extension.
|
||||||
|
*/
|
||||||
|
YAML;
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,8 @@ public abstract class Parseable implements ConfigParseable {
|
|||||||
ConfigParseOptions finalOptions) throws IOException {
|
ConfigParseOptions finalOptions) throws IOException {
|
||||||
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
|
if (finalOptions.getSyntax() == ConfigSyntax.PROPERTIES) {
|
||||||
return PropertiesParser.parse(reader, origin);
|
return PropertiesParser.parse(reader, origin);
|
||||||
|
} else if( finalOptions.getSyntax() == ConfigSyntax.YAML ) {
|
||||||
|
return YAMLParser.parse(reader, origin);
|
||||||
} else {
|
} else {
|
||||||
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
|
Iterator<Token> tokens = Tokenizer.tokenize(origin, reader, finalOptions.getSyntax());
|
||||||
ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions);
|
ConfigNodeRoot document = ConfigDocumentParser.parse(tokens, origin, finalOptions);
|
||||||
|
@ -166,7 +166,7 @@ class SimpleIncluder implements FullIncluder {
|
|||||||
// loading app.{conf,json,properties} from the filesystem.
|
// loading app.{conf,json,properties} from the filesystem.
|
||||||
static ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) {
|
static ConfigObject fromBasename(NameSource source, String name, ConfigParseOptions options) {
|
||||||
ConfigObject obj;
|
ConfigObject obj;
|
||||||
if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties")) {
|
if (name.endsWith(".conf") || name.endsWith(".json") || name.endsWith(".properties") || name.endsWith(".yml")) {
|
||||||
ConfigParseable p = source.nameToParseable(name, options);
|
ConfigParseable p = source.nameToParseable(name, options);
|
||||||
|
|
||||||
obj = p.parse(p.options().setAllowMissing(options.getAllowMissing()));
|
obj = p.parse(p.options().setAllowMissing(options.getAllowMissing()));
|
||||||
@ -174,6 +174,8 @@ class SimpleIncluder implements FullIncluder {
|
|||||||
ConfigParseable confHandle = source.nameToParseable(name + ".conf", options);
|
ConfigParseable confHandle = source.nameToParseable(name + ".conf", options);
|
||||||
ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options);
|
ConfigParseable jsonHandle = source.nameToParseable(name + ".json", options);
|
||||||
ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options);
|
ConfigParseable propsHandle = source.nameToParseable(name + ".properties", options);
|
||||||
|
ConfigParseable ymlHandle = source.nameToParseable(name + ".yml", options);
|
||||||
|
|
||||||
boolean gotSomething = false;
|
boolean gotSomething = false;
|
||||||
List<ConfigException.IO> fails = new ArrayList<ConfigException.IO>();
|
List<ConfigException.IO> fails = new ArrayList<ConfigException.IO>();
|
||||||
|
|
||||||
@ -200,6 +202,17 @@ class SimpleIncluder implements FullIncluder {
|
|||||||
fails.add(e);
|
fails.add(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (syntax == null || syntax == ConfigSyntax.YAML) {
|
||||||
|
try {
|
||||||
|
ConfigObject parsed = ymlHandle.parse(jsonHandle.options()
|
||||||
|
.setAllowMissing(false).setSyntax(ConfigSyntax.YAML));
|
||||||
|
obj = obj.withFallback(parsed);
|
||||||
|
gotSomething = true;
|
||||||
|
} catch (ConfigException.IO e) {
|
||||||
|
fails.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (syntax == null || syntax == ConfigSyntax.PROPERTIES) {
|
if (syntax == null || syntax == ConfigSyntax.PROPERTIES) {
|
||||||
try {
|
try {
|
||||||
@ -217,7 +230,7 @@ class SimpleIncluder implements FullIncluder {
|
|||||||
// the individual exceptions should have been logged already
|
// the individual exceptions should have been logged already
|
||||||
// with tracing enabled
|
// with tracing enabled
|
||||||
ConfigImpl.trace("Did not find '" + name
|
ConfigImpl.trace("Did not find '" + name
|
||||||
+ "' with any extension (.conf, .json, .properties); "
|
+ "' with any extension (.conf, .json, .properties, .yml); "
|
||||||
+ "exceptions should have been logged above.");
|
+ "exceptions should have been logged above.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +251,7 @@ class SimpleIncluder implements FullIncluder {
|
|||||||
} else if (!gotSomething) {
|
} else if (!gotSomething) {
|
||||||
if (ConfigImpl.traceLoadsEnabled()) {
|
if (ConfigImpl.traceLoadsEnabled()) {
|
||||||
ConfigImpl.trace("Did not find '" + name
|
ConfigImpl.trace("Did not find '" + name
|
||||||
+ "' with any extension (.conf, .json, .properties); but '" + name
|
+ "' with any extension (.conf, .json, .properties, .yml); but '" + name
|
||||||
+ "' is allowed to be missing. Exceptions from load attempts should have been logged above.");
|
+ "' is allowed to be missing. Exceptions from load attempts should have been logged above.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
198
config/src/main/java/com/typesafe/config/impl/YAMLParser.java
Normal file
198
config/src/main/java/com/typesafe/config/impl/YAMLParser.java
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
|
||||||
|
*/
|
||||||
|
package com.typesafe.config.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigException;
|
||||||
|
import com.typesafe.config.ConfigOrigin;
|
||||||
|
|
||||||
|
|
||||||
|
final class YAMLParser {
|
||||||
|
|
||||||
|
static AbstractConfigObject parse(Reader reader,
|
||||||
|
ConfigOrigin origin) throws IOException {
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
Map<Object, Object> yamlMap = (Map<Object, Object>) yaml.load(reader);
|
||||||
|
|
||||||
|
Map<Path, Object> pathMap = new HashMap<Path, Object>();
|
||||||
|
for (Map.Entry<Object, Object> entry : yamlMap.entrySet()) {
|
||||||
|
Object key = entry.getKey();
|
||||||
|
if (key instanceof String) {
|
||||||
|
Path path = pathFromPropertyKey((String) key);
|
||||||
|
pathMap.put(path, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fromPathMap(origin, pathMap, true /* from properties */);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static String lastElement(String path) {
|
||||||
|
int i = path.lastIndexOf('.');
|
||||||
|
if (i < 0)
|
||||||
|
return path;
|
||||||
|
else
|
||||||
|
return path.substring(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String exceptLastElement(String path) {
|
||||||
|
int i = path.lastIndexOf('.');
|
||||||
|
if (i < 0)
|
||||||
|
return null;
|
||||||
|
else
|
||||||
|
return path.substring(0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Path pathFromPropertyKey(String key) {
|
||||||
|
String last = lastElement(key);
|
||||||
|
String exceptLast = exceptLastElement(key);
|
||||||
|
Path path = new Path(last, null);
|
||||||
|
while (exceptLast != null) {
|
||||||
|
last = lastElement(exceptLast);
|
||||||
|
exceptLast = exceptLastElement(exceptLast);
|
||||||
|
path = new Path(last, path);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AbstractConfigObject fromPathMap(ConfigOrigin origin,
|
||||||
|
Map<?, ?> pathExpressionMap) {
|
||||||
|
Map<Path, Object> pathMap = new HashMap<Path, Object>();
|
||||||
|
for (Map.Entry<?, ?> entry : pathExpressionMap.entrySet()) {
|
||||||
|
Object keyObj = entry.getKey();
|
||||||
|
if (!(keyObj instanceof String)) {
|
||||||
|
throw new ConfigException.BugOrBroken(
|
||||||
|
"Map has a non-string as a key, expecting a path expression as a String");
|
||||||
|
}
|
||||||
|
Path path = Path.newPath((String) keyObj);
|
||||||
|
pathMap.put(path, entry.getValue());
|
||||||
|
}
|
||||||
|
return fromPathMap(origin, pathMap, false /* from properties */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AbstractConfigObject fromPathMap(ConfigOrigin origin,
|
||||||
|
Map<Path, Object> pathMap, boolean convertedFromProperties) {
|
||||||
|
/*
|
||||||
|
* First, build a list of paths that will have values, either string or
|
||||||
|
* object values.
|
||||||
|
*/
|
||||||
|
Set<Path> scopePaths = new HashSet<Path>();
|
||||||
|
Set<Path> valuePaths = new HashSet<Path>();
|
||||||
|
for (Path path : pathMap.keySet()) {
|
||||||
|
// add value's path
|
||||||
|
valuePaths.add(path);
|
||||||
|
|
||||||
|
// all parent paths are objects
|
||||||
|
Path next = path.parent();
|
||||||
|
while (next != null) {
|
||||||
|
scopePaths.add(next);
|
||||||
|
next = next.parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (convertedFromProperties) {
|
||||||
|
/*
|
||||||
|
* If any string values are also objects containing other values,
|
||||||
|
* drop those string values - objects "win".
|
||||||
|
*/
|
||||||
|
valuePaths.removeAll(scopePaths);
|
||||||
|
} else {
|
||||||
|
/* If we didn't start out as properties, then this is an error. */
|
||||||
|
for (Path path : valuePaths) {
|
||||||
|
if (scopePaths.contains(path)) {
|
||||||
|
throw new ConfigException.BugOrBroken(
|
||||||
|
"In the map, path '"
|
||||||
|
+ path.render()
|
||||||
|
+ "' occurs as both the parent object of a value and as a value. "
|
||||||
|
+ "Because Map has no defined ordering, this is a broken situation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create maps for the object-valued values.
|
||||||
|
*/
|
||||||
|
Map<String, AbstractConfigValue> root = new HashMap<String, AbstractConfigValue>();
|
||||||
|
Map<Path, Map<String, AbstractConfigValue>> scopes = new HashMap<Path, Map<String, AbstractConfigValue>>();
|
||||||
|
|
||||||
|
for (Path path : scopePaths) {
|
||||||
|
Map<String, AbstractConfigValue> scope = new HashMap<String, AbstractConfigValue>();
|
||||||
|
scopes.put(path, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store string values in the associated scope maps */
|
||||||
|
for (Path path : valuePaths) {
|
||||||
|
Path parentPath = path.parent();
|
||||||
|
Map<String, AbstractConfigValue> parent = parentPath != null ? scopes
|
||||||
|
.get(parentPath) : root;
|
||||||
|
|
||||||
|
String last = path.last();
|
||||||
|
Object rawValue = pathMap.get(path);
|
||||||
|
AbstractConfigValue value;
|
||||||
|
if (convertedFromProperties) {
|
||||||
|
if (rawValue instanceof String) {
|
||||||
|
value = new ConfigString(origin, (String) rawValue);
|
||||||
|
} else {
|
||||||
|
// silently ignore non-string values in Properties
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = ConfigImpl.fromAnyRef(pathMap.get(path), origin,
|
||||||
|
FromMapMode.KEYS_ARE_PATHS);
|
||||||
|
}
|
||||||
|
if (value != null)
|
||||||
|
parent.put(last, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a list of scope paths from longest to shortest, so children go
|
||||||
|
* before parents.
|
||||||
|
*/
|
||||||
|
List<Path> sortedScopePaths = new ArrayList<Path>();
|
||||||
|
sortedScopePaths.addAll(scopePaths);
|
||||||
|
// sort descending by length
|
||||||
|
Collections.sort(sortedScopePaths, new Comparator<Path>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Path a, Path b) {
|
||||||
|
// Path.length() is O(n) so in theory this sucks
|
||||||
|
// but in practice we can make Path precompute length
|
||||||
|
// if it ever matters.
|
||||||
|
return b.length() - a.length();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create ConfigObject for each scope map, working from children to
|
||||||
|
* parents to avoid modifying any already-created ConfigObject. This is
|
||||||
|
* where we need the sorted list.
|
||||||
|
*/
|
||||||
|
for (Path scopePath : sortedScopePaths) {
|
||||||
|
Map<String, AbstractConfigValue> scope = scopes.get(scopePath);
|
||||||
|
|
||||||
|
Path parentPath = scopePath.parent();
|
||||||
|
Map<String, AbstractConfigValue> parent = parentPath != null ? scopes
|
||||||
|
.get(parentPath) : root;
|
||||||
|
|
||||||
|
AbstractConfigObject o = new SimpleConfigObject(origin, scope,
|
||||||
|
ResolveStatus.RESOLVED, false /* ignoresFallbacks */);
|
||||||
|
parent.put(scopePath.last(), o);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return root config object
|
||||||
|
return new SimpleConfigObject(origin, root, ResolveStatus.RESOLVED,
|
||||||
|
false /* ignoresFallbacks */);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user