Fix code to find includes relative to other includes. Support ParseOptions for load().

This commit is contained in:
Havoc Pennington 2011-11-14 23:52:14 -05:00
parent d257c3bbd9
commit 6ca952e516
14 changed files with 171 additions and 38 deletions

View File

@ -52,6 +52,12 @@ public final class Config {
return loadWithoutResolving(rootPath).resolve(); return loadWithoutResolving(rootPath).resolve();
} }
public static ConfigRoot load(String rootPath,
ConfigParseOptions parseOptions, ConfigResolveOptions resolveOptions) {
return loadWithoutResolving(rootPath, parseOptions).resolve(
resolveOptions);
}
/** /**
* Like load() but does not resolve the object, so you can go ahead and add * Like load() but does not resolve the object, so you can go ahead and add
* more fallbacks and stuff and have them seen by substitutions when you do * more fallbacks and stuff and have them seen by substitutions when you do
@ -61,11 +67,15 @@ public final class Config {
* @return * @return
*/ */
public static ConfigRoot loadWithoutResolving(String rootPath) { public static ConfigRoot loadWithoutResolving(String rootPath) {
return loadWithoutResolving(rootPath, ConfigParseOptions.defaults());
}
public static ConfigRoot loadWithoutResolving(String rootPath,
ConfigParseOptions options) {
ConfigRoot system = systemPropertiesRoot(rootPath); ConfigRoot system = systemPropertiesRoot(rootPath);
ConfigValue mainFiles = parse(rootPath, ConfigParseOptions.defaults()); ConfigValue mainFiles = parse(rootPath, options);
ConfigValue referenceFiles = parse(rootPath + ".reference", ConfigValue referenceFiles = parse(rootPath + ".reference", options);
ConfigParseOptions.defaults());
return system.withFallbacks(mainFiles, referenceFiles); return system.withFallbacks(mainFiles, referenceFiles);
} }

View File

@ -28,6 +28,7 @@ public class ConfigImpl {
if (name.endsWith(".conf") || name.endsWith(".json") if (name.endsWith(".conf") || name.endsWith(".json")
|| name.endsWith(".properties")) { || name.endsWith(".properties")) {
ConfigParseable p = source.nameToParseable(name); ConfigParseable p = source.nameToParseable(name);
if (p != null) { if (p != null) {
obj = p.parse(p.options().setAllowMissing( obj = p.parse(p.options().setAllowMissing(
options.getAllowMissing())); options.getAllowMissing()));
@ -47,17 +48,30 @@ public class ConfigImpl {
"No config files {.conf,.json,.properties} found"); "No config files {.conf,.json,.properties} found");
} }
ConfigSyntax syntax = options.getSyntax();
obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name)); obj = SimpleConfigObject.empty(new SimpleConfigOrigin(name));
if (confHandle != null) if (confHandle != null
&& (syntax == null || syntax == ConfigSyntax.CONF)) {
obj = confHandle.parse(confHandle.options() obj = confHandle.parse(confHandle.options()
.setAllowMissing(true).setSyntax(ConfigSyntax.CONF)); .setAllowMissing(true).setSyntax(ConfigSyntax.CONF));
if (jsonHandle != null) }
obj = obj.withFallback(jsonHandle.parse(jsonHandle.options()
.setAllowMissing(true).setSyntax(ConfigSyntax.JSON))); if (jsonHandle != null
if (propsHandle != null) && (syntax == null || syntax == ConfigSyntax.JSON)) {
obj = obj.withFallback(propsHandle.parse(propsHandle.options() ConfigObject parsed = jsonHandle.parse(jsonHandle
.options().setAllowMissing(true)
.setSyntax(ConfigSyntax.JSON));
obj = obj.withFallback(parsed);
}
if (propsHandle != null
&& (syntax == null || syntax == ConfigSyntax.PROPERTIES)) {
ConfigObject parsed = propsHandle.parse(propsHandle.options()
.setAllowMissing(true) .setAllowMissing(true)
.setSyntax(ConfigSyntax.PROPERTIES))); .setSyntax(ConfigSyntax.PROPERTIES));
obj = obj.withFallback(parsed);
}
} }
return obj; return obj;

View File

@ -200,39 +200,28 @@ public abstract class Parseable implements ConfigParseable {
}; };
} }
private static URL urlParent(URL url) {
String path = url.getPath();
if (path == null)
return null;
File f = new File(path);
String parent = f.getParent();
try {
return new URL(url.getProtocol(), url.getHost(), url.getPort(),
parent);
} catch (MalformedURLException e) {
return null;
}
}
static URL relativeTo(URL url, String filename) { static URL relativeTo(URL url, String filename) {
// I'm guessing this completely fails on Windows, help wanted // I'm guessing this completely fails on Windows, help wanted
if (new File(filename).isAbsolute()) if (new File(filename).isAbsolute())
return null; return null;
URL parentURL = urlParent(url);
if (parentURL == null)
return null;
try { try {
URI parent = parentURL.toURI(); URI siblingURI = url.toURI();
URI relative = new URI(null, null, "/" + filename, null); URI relative = new URI(filename);
return parent.relativize(relative).toURL();
// this seems wrong, but it's documented that the last
// element of the path in siblingURI gets stripped out,
// so to get something in the same directory as
// siblingURI we just call resolve().
URL resolved = siblingURI.resolve(relative).toURL();
return resolved;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
return null; return null;
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
return null; return null;
} catch (IllegalArgumentException e) {
return null;
} }
} }
@ -335,7 +324,10 @@ public abstract class Parseable implements ConfigParseable {
@Override @Override
ConfigParseable relativeTo(String filename) { ConfigParseable relativeTo(String filename) {
return newURL(relativeTo(input, filename), options() URL url = relativeTo(input, filename);
if (url == null)
return null;
return newURL(url, options()
.setOriginDescription(null)); .setOriginDescription(null));
} }
@ -382,8 +374,10 @@ public abstract class Parseable implements ConfigParseable {
@Override @Override
ConfigParseable relativeTo(String filename) { ConfigParseable relativeTo(String filename) {
try { try {
return newURL(relativeTo(input.toURI().toURL(), filename), URL url = relativeTo(input.toURI().toURL(), filename);
options().setOriginDescription(null)); if (url == null)
return null;
return newURL(url, options().setOriginDescription(null));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
return null; return null;
} }

View File

@ -0,0 +1,5 @@
letters {
include "letters/a.conf"
include "letters/b.json"
include "letters/c"
}

View File

@ -0,0 +1,6 @@
numbers {
include "numbers/1.conf"
include "numbers/2"
}
a=ok

View File

@ -0,0 +1,3 @@
{
"b" : 507
}

View File

@ -0,0 +1,2 @@
c.fromConf=89

View File

@ -0,0 +1 @@
c.fromProp=true

View File

@ -0,0 +1 @@
"1"=1

View File

@ -0,0 +1 @@
2=abcd

View File

@ -0,0 +1,14 @@
{
"letters" : {
"numbers" : {
"2" : "abcd",
"1" : 1
},
"a" : "ok",
"b" : 507,
"c" : {
"fromConf" : 89,
"fromProp" : true
}
}
}

View File

@ -13,5 +13,13 @@
"equiv01" : { "equiv01" : {
include "equiv01/original.json" include "equiv01/original.json"
},
# missing includes are supposed to be silently ignored
nonexistent {
include "nothere"
include "nothere.conf"
include "nothere.json"
include "nothere.properties"
} }
} }

View File

@ -87,8 +87,8 @@ class EquivalentsTest extends TestUtils {
// This is a little "checksum" to be sure we really tested what we were expecting. // This is a little "checksum" to be sure we really tested what we were expecting.
// it breaks every time you add a file, so you have to update it. // it breaks every time you add a file, so you have to update it.
assertEquals(2, dirCount) assertEquals(3, dirCount)
// this is the number of files not named original.* // this is the number of files not named original.*
assertEquals(12, fileCount) assertEquals(13, fileCount)
} }
} }

View File

@ -0,0 +1,74 @@
package com.typesafe.config.impl
import org.junit.Assert._
import org.junit._
import scala.collection.JavaConverters._
import com.typesafe.config._
class PublicApiTest extends TestUtils {
@Test
def basicLoadAndGet() {
val conf = Config.load("test01")
val a = conf.getInt("ints.fortyTwo")
val obj = conf.getObject("ints")
val c = obj.getInt("fortyTwo")
val ms = conf.getMilliseconds("durations.halfSecond")
// should have used system variables
if (System.getenv("HOME") != null)
assertEquals(System.getenv("HOME"), conf.getString("system.home"))
assertEquals(System.getProperty("java.version"), conf.getString("system.javaversion"))
}
@Test
def noSystemVariables() {
// should not have used system variables
val conf = Config.load("test01", ConfigParseOptions.defaults(),
ConfigResolveOptions.noSystem())
intercept[ConfigException.Null] {
conf.getString("system.home")
}
intercept[ConfigException.Null] {
conf.getString("system.javaversion")
}
}
@Test
def canLimitLoadToJson {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(1, conf.getInt("fromJson1"))
intercept[ConfigException.Missing] {
conf.getInt("ints.fortyTwo")
}
}
@Test
def canLimitLoadToProperties {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.PROPERTIES);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(1, conf.getInt("fromProps.one"))
intercept[ConfigException.Missing] {
conf.getInt("ints.fortyTwo")
}
}
@Test
def canLimitLoadToConf {
val options = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF);
val conf = Config.load("test01", options, ConfigResolveOptions.defaults())
assertEquals(42, conf.getInt("ints.fortyTwo"))
intercept[ConfigException.Missing] {
conf.getInt("fromJson1")
}
intercept[ConfigException.Missing] {
conf.getInt("fromProps.one")
}
}
}