Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ian Tabolt 2017-10-06 11:00:12 -04:00
commit 3d44d90d4d
30 changed files with 916 additions and 163 deletions

View File

@ -30,7 +30,7 @@
- [Include syntax](#include-syntax)
- [Include semantics: merging](#include-semantics-merging)
- [Include semantics: substitution](#include-semantics-substitution)
- [Include semantics: missing files](#include-semantics-missing-files)
- [Include semantics: missing files and required files](#include-semantics-missing-files-and-required-files)
- [Include semantics: file formats and extensions](#include-semantics-file-formats-and-extensions)
- [Include semantics: locating resources](#include-semantics-locating-resources)
- [Conversion of numerically-indexed objects to arrays](#conversion-of-numerically-indexed-objects-to-arrays)
@ -47,6 +47,7 @@
- [Substitution fallback to environment variables](#substitution-fallback-to-environment-variables)
- [hyphen-separated vs. camelCase](#hyphen-separated-vs-camelcase)
- [Note on Java properties similarity](#note-on-java-properties-similarity)
- [Note on Windows and case sensitivity of environment variables](#note-on-windows-and-case-sensitivity-of-environment-variables)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -1295,7 +1296,27 @@ must be lowercase. Exactly these strings are supported:
- `m`, `minute`, `minutes`
- `h`, `hour`, `hours`
- `d`, `day`, `days`
### Period Format
Similar to the `getDuration()` method, there is a `getPeriod()` method
available for getting time units as a `java.time.Period`.
This can use the general "units format" described above; bare
numbers are taken to be in days, while strings are
parsed as a number plus an optional unit string.
The supported unit strings for period are case sensitive and
must be lowercase. Exactly these strings are supported:
- `d`, `day`, `days`
- `w`, `week`, `weeks`
- `m`, `mo`, `month`, `months` (note that if you are using `getTemporal()`
which may return either a `java.time.Duration` or a `java.time.Period`
you will want to use `mo` rather than `m` to prevent your unit being
parsed as minutes)
- `y`, `year`, `years`
### Size in bytes format
Implementations may wish to support a `getBytes()` returning a

14
NEWS.md
View File

@ -1,3 +1,17 @@
# 1.3.2: October 6, 2017
- environment variables are now able to be resolved to lists in
the same fashion as system properties.
- added `getPeriod()` which returns time units as
`java.time.Period`. Currently supported periods are days, weeks,
months and years. [More information here](HOCON.md#period-format).
- `ConfigResolveOptions` now has `appendResolver(...)` which allows
having custom behavior when unresolved substitutions are encountered
during resolution.
- Config Beans now support `Set` collection.
- a few other small bugfixes. All of the fixed issues can be found
in the [milestone page](https://github.com/typesafehub/config/milestone/1?closed=1).
# 1.3.1: September 24, 2016
- added `include required("foo")` syntax to specify includes that

View File

@ -81,11 +81,13 @@ to merge it in.
- [Java (yep!) wrappers for the Java library](#java-yep-wrappers-for-the-java-library)
- [Scala wrappers for the Java library](#scala-wrappers-for-the-java-library)
- [Clojure wrappers for the Java library](#clojure-wrappers-for-the-java-library)
- [Kotlin wrappers for the Java library](#kotlin-wrappers-for-the-java-library)
- [Scala port](#scala-port)
- [Ruby port](#ruby-port)
- [Puppet module](#puppet-module)
- [Python port](#python-port)
- [C++ port](#c-port)
- [JavaScript port](#javascript-port)
- [Linting tool](#linting-tool)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -196,7 +198,7 @@ There isn't a schema language or anything like that. However, two
suggested tools are:
- use the
[checkValid() method](http://typesafehub.github.com/config/latest/api/com/typesafe/config/Config.html#checkValid%28com.typesafe.config.Config,%20java.lang.String...%29)
[checkValid() method](http://typesafehub.github.io/config/latest/api/com/typesafe/config/Config.html#checkValid-com.typesafe.config.Config-java.lang.String...-)
- access your config through a Settings class with a field for
each setting, and instantiate it on startup (immediately
throwing an exception if any settings are missing)
@ -251,7 +253,8 @@ library examples in `examples/` show how to accept a custom config
while defaulting to `ConfigFactory.load()`.
For applications using `application.{conf,json,properties}`,
system properties can be used to force a different config source:
system properties can be used to force a different config source
(e.g. from command line `-Dconfig.file=path/to/config-file`):
- `config.resource` specifies a resource name - not a
basename, i.e. `application.conf` not `application`
@ -837,16 +840,23 @@ format.
* Ficus https://github.com/ceedubs/ficus
* configz https://github.com/arosien/configz
* configs https://github.com/kxbmap/configs
* config-annotation https://github.com/wacai/config-annotation
* PureConfig https://github.com/melrief/pureconfig
 * config-annotation https://github.com/zhongl/config-annotation
* PureConfig https://github.com/pureconfig/pureconfig
* Simple Scala Config https://github.com/ElderResearch/ssc
* konfig https://github.com/vpon/konfig
* ScalaConfig https://github.com/andr83/scalaconfig
* static-config https://github.com/Krever/static-config
* validated-config https://github.com/carlpulley/validated-config
* Cedi Config https://github.com/ccadllc/cedi-config
* Cfg https://github.com/carueda/cfg
#### Clojure wrappers for the Java library
* beamly-core.config https://github.com/beamly/beamly-core.config
#### Kotlin wrappers for the Java library
* config4k https://github.com/config4k/config4k
#### Scala port
* SHocon https://github.com/unicredit/shocon (work with both Scala and Scala.Js)
@ -867,7 +877,10 @@ format.
* https://github.com/puppetlabs/cpp-hocon
#### JavaScript port
* https://github.com/yellowblood/hocon-js (missing features, under development)
#### Linting tool
* A web based linting tool http://www.hoconlint.com/

View File

@ -13,6 +13,58 @@ scalacOptions in GlobalScope in Test := Seq("-unchecked", "-deprecation", "-feat
scalaVersion in ThisBuild := "2.10.4"
val sonatype = new PublishToSonatype {
def projectUrl = "https://github.com/typesafehub/config"
def developerId = "havocp"
def developerName = "Havoc Pennington"
def developerUrl = "http://ometer.com/"
def scmUrl = "git://github.com/typesafehub/config.git"
}
lazy val commonSettings: Seq[Setting[_]] = Def.settings(
unpublished,
javaVersionPrefix in javaVersionCheck := None
)
lazy val root = (project in file("."))
.settings(
commonSettings,
aggregate in doc := false,
doc := (doc in (configLib, Compile)).value,
aggregate in packageDoc := false,
packageDoc := (packageDoc in (configLib, Compile)).value,
aggregate in checkstyle := false,
checkstyle := (checkstyle in (configLib, Compile)).value
)
.aggregate(
testLib, configLib,
simpleLibScala, simpleAppScala, complexAppScala,
simpleLibJava, simpleAppJava, complexAppJava
)
lazy val configLib = Project("config", file("config"))
.settings(
sonatype.settings,
osgiSettings,
OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl"),
publish := sys.error("use publishSigned instead of plain publish"),
publishLocal := sys.error("use publishLocalSigned instead of plain publishLocal")
)
.enablePlugins(SbtOsgi)
.dependsOn(testLib % "test->test")
def proj(id: String, base: File) = Project(id, base) settings commonSettings
lazy val testLib = proj("config-test-lib", file("test-lib"))
lazy val simpleLibScala = proj("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib
lazy val simpleAppScala = proj("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala
lazy val complexAppScala = proj("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala
lazy val simpleLibJava = proj("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib
lazy val simpleAppJava = proj("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava
lazy val complexAppJava = proj("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava
useGpg := true
aggregate in PgpKeys.publishSigned := false
@ -20,3 +72,16 @@ PgpKeys.publishSigned := (PgpKeys.publishSigned in configLib).value
aggregate in PgpKeys.publishLocalSigned := false
PgpKeys.publishLocalSigned := (PgpKeys.publishLocalSigned in configLib).value
val unpublished = Seq(
// no artifacts in this project
publishArtifact := false,
// make-pom has a more specific publishArtifact setting already
// so needs specific override
publishArtifact in makePom := false,
// no docs to publish
publishArtifact in packageDoc := false,
// can't seem to get rid of ivy files except by no-op'ing the entire publish task
publish := {},
publishLocal := {}
)

View File

@ -14,9 +14,13 @@ ScalariformKeys.preferences in Compile := formatPrefs
ScalariformKeys.preferences in Test := formatPrefs
fork in test := true
fork in Test := true
fork in run := true
fork in run in Test := true
//env vars for tests
envVars in Test ++= Map("testList.0" -> "0", "testList.1" -> "1")
autoScalaLibrary := false
crossPaths := false
@ -58,7 +62,7 @@ checkstyle in Compile := {
}
// add checkstyle as a dependency of doc
doc in Compile <<= (doc in Compile).dependsOn(checkstyle in Compile)
doc in Compile := ((doc in Compile).dependsOn(checkstyle in Compile)).value
findbugsSettings
findbugsReportType := Some(ReportType.Html)

View File

@ -4,6 +4,8 @@
package com.typesafe.config;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.TemporalAmount;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -793,6 +795,44 @@ public interface Config extends ConfigMergeable {
*/
Duration getDuration(String path);
/**
* Gets a value as a java.time.Period. If the value is
* already a number, then it's taken as days; if it's
* a string, it's parsed understanding units suffixes like
* "10d" or "5w" as documented in the <a
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
* spec</a>. This method never returns null.
*
* @since 1.3.0
*
* @param path
* path expression
* @return the period value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to Long or String
* @throws ConfigException.BadValue
* if value cannot be parsed as a number of the given TimeUnit
*/
Period getPeriod(String path);
/**
* Gets a value as a java.time.temporal.TemporalAmount.
* This method will first try get get the value as a java.time.Duration, and if unsuccessful,
* then as a java.time.Period.
* This means that values like "5m" will be parsed as 5 minutes rather than 5 months
* @param path path expression
* @return the temporal value at the requested path
* @throws ConfigException.Missing
* if value is absent or null
* @throws ConfigException.WrongType
* if value is not convertible to Long or String
* @throws ConfigException.BadValue
* if value cannot be parsed as a TemporalAmount
*/
TemporalAmount getTemporal(String path);
/**
* Gets a list value (with any element type) as a {@link ConfigList}, which
* implements {@code java.util.List<ConfigValue>}. Throws if the path is

View File

@ -496,6 +496,7 @@ public final class ConfigFactory {
// We rely on this having the side effect that it drops
// all caches
ConfigImpl.reloadSystemPropertiesConfig();
ConfigImpl.reloadEnvVariablesConfig();
}
/**

View File

@ -29,10 +29,13 @@ package com.typesafe.config;
public final class ConfigResolveOptions {
private final boolean useSystemEnvironment;
private final boolean allowUnresolved;
private final ConfigResolver resolver;
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved) {
private ConfigResolveOptions(boolean useSystemEnvironment, boolean allowUnresolved,
ConfigResolver resolver) {
this.useSystemEnvironment = useSystemEnvironment;
this.allowUnresolved = allowUnresolved;
this.resolver = resolver;
}
/**
@ -42,7 +45,7 @@ public final class ConfigResolveOptions {
* @return the default resolve options
*/
public static ConfigResolveOptions defaults() {
return new ConfigResolveOptions(true, false);
return new ConfigResolveOptions(true, false, NULL_RESOLVER);
}
/**
@ -64,7 +67,7 @@ public final class ConfigResolveOptions {
* @return options with requested setting for use of environment variables
*/
public ConfigResolveOptions setUseSystemEnvironment(boolean value) {
return new ConfigResolveOptions(value, allowUnresolved);
return new ConfigResolveOptions(value, allowUnresolved, resolver);
}
/**
@ -91,7 +94,55 @@ public final class ConfigResolveOptions {
* @since 1.2.0
*/
public ConfigResolveOptions setAllowUnresolved(boolean value) {
return new ConfigResolveOptions(useSystemEnvironment, value);
return new ConfigResolveOptions(useSystemEnvironment, value, resolver);
}
/**
* Returns options where the given resolver used as a fallback if a
* reference cannot be otherwise resolved. This resolver will only be called
* after resolution has failed to substitute with a value from within the
* config itself and with any other resolvers that have been appended before
* this one. Multiple resolvers can be added using,
*
* <pre>
* ConfigResolveOptions options = ConfigResolveOptions.defaults()
* .appendResolver(primary)
* .appendResolver(secondary)
* .appendResolver(tertiary);
* </pre>
*
* With this config unresolved references will first be resolved with the
* primary resolver, if that fails then the secondary, and finally if that
* also fails the tertiary.
*
* If all fallbacks fail to return a substitution "allow unresolved"
* determines whether resolution fails or continues.
*`
* @param value the resolver to fall back to
* @return options that use the given resolver as a fallback
* @since 1.3.2
*/
public ConfigResolveOptions appendResolver(ConfigResolver value) {
if (value == null) {
throw new ConfigException.BugOrBroken("null resolver passed to appendResolver");
} else if (value == this.resolver) {
return this;
} else {
return new ConfigResolveOptions(useSystemEnvironment, allowUnresolved,
this.resolver.withFallback(value));
}
}
/**
* Returns the resolver to use as a fallback if a substitution cannot be
* otherwise resolved. Never returns null. This method is mostly used by the
* config lib internally, not by applications.
*
* @return the non-null fallback resolver
* @since 1.3.2
*/
public ConfigResolver getResolver() {
return this.resolver;
}
/**
@ -104,4 +155,22 @@ public final class ConfigResolveOptions {
public boolean getAllowUnresolved() {
return allowUnresolved;
}
/**
* Singleton resolver that never resolves paths.
*/
private static final ConfigResolver NULL_RESOLVER = new ConfigResolver() {
@Override
public ConfigValue lookup(String path) {
return null;
}
@Override
public ConfigResolver withFallback(ConfigResolver fallback) {
return fallback;
}
};
}

View File

@ -0,0 +1,38 @@
package com.typesafe.config;
/**
* Implement this interface and provide an instance to
* {@link ConfigResolveOptions#appendResolver ConfigResolveOptions.appendResolver()}
* to provide custom behavior when unresolved substitutions are encountered
* during resolution.
* @since 1.3.2
*/
public interface ConfigResolver {
/**
* Returns the value to substitute for the given unresolved path. To get the
* components of the path use {@link ConfigUtil#splitPath(String)}. If a
* non-null value is returned that value will be substituted, otherwise
* resolution will continue to consider the substitution as still
* unresolved.
*
* @param path the unresolved path
* @return the value to use as a substitution or null
*/
public ConfigValue lookup(String path);
/**
* Returns a new resolver that falls back to the given resolver if this
* one doesn't provide a substitution itself.
*
* It's important to handle the case where you already have the fallback
* with a "return this", i.e. this method should not create a new object if
* the fallback is the same one you already have. The same fallback may be
* added repeatedly.
*
* @param fallback the previous includer for chaining
* @return a new resolver
*/
public ConfigResolver withFallback(ConfigResolver fallback);
}

View File

@ -11,9 +11,11 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.time.Duration;
import java.util.Set;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
@ -160,6 +162,8 @@ public class ConfigBeanImpl {
return config.getAnyRef(configPropName);
} else if (parameterClass == List.class) {
return getListValue(beanClass, parameterType, parameterClass, config, configPropName);
} else if (parameterClass == Set.class) {
return getSetValue(beanClass, parameterType, parameterClass, config, configPropName);
} else if (parameterClass == Map.class) {
// we could do better here, but right now we don't.
Type[] typeArgs = ((ParameterizedType)parameterType).getActualTypeArguments();
@ -186,6 +190,10 @@ public class ConfigBeanImpl {
}
}
private static Object getSetValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPropName) {
return new HashSet((List) getListValue(beanClass, parameterType, parameterClass, config, configPropName));
}
private static Object getListValue(Class<?> beanClass, Type parameterType, Class<?> parameterClass, Config config, String configPropName) {
Type elementType = ((ParameterizedType)parameterType).getActualTypeArguments()[0];
@ -277,7 +285,7 @@ public class ConfigBeanImpl {
private static boolean isOptionalProperty(Class beanClass, PropertyDescriptor beanProp) {
Field field = getField(beanClass, beanProp.getName());
return (field.getAnnotationsByType(Optional.class).length > 0);
return field != null && (field.getAnnotationsByType(Optional.class).length > 0);
}
private static Field getField(Class beanClass, String fieldName) {

View File

@ -335,20 +335,11 @@ public class ConfigImpl {
}
private static AbstractConfigObject loadEnvVariables() {
Map<String, String> env = System.getenv();
Map<String, AbstractConfigValue> m = new HashMap<String, AbstractConfigValue>();
for (Map.Entry<String, String> entry : env.entrySet()) {
String key = entry.getKey();
m.put(key,
new ConfigString.Quoted(SimpleConfigOrigin.newSimple("env var " + key), entry
.getValue()));
}
return new SimpleConfigObject(SimpleConfigOrigin.newSimple("env variables"),
m, ResolveStatus.RESOLVED, false /* ignoresFallbacks */);
return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv());
}
private static class EnvVariablesHolder {
static final AbstractConfigObject envVariables = loadEnvVariables();
static volatile AbstractConfigObject envVariables = loadEnvVariables();
}
static AbstractConfigObject envVariablesAsConfigObject() {
@ -363,6 +354,12 @@ public class ConfigImpl {
return envVariablesAsConfigObject().toConfig();
}
public static void reloadEnvVariablesConfig() {
// ConfigFactory.invalidateCaches() relies on this having the side
// effect that it drops all caches
EnvVariablesHolder.envVariables = loadEnvVariables();
}
public static Config defaultReference(final ClassLoader loader) {
return computeCachedConfig(loader, "defaultReference", new Callable<Config>() {
@Override

View File

@ -6,6 +6,8 @@ import java.util.Collections;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigRenderOptions;
import com.typesafe.config.ConfigResolveOptions;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
/**
@ -88,7 +90,8 @@ final class ConfigReference extends AbstractConfigValue implements Unmergeable {
v = result.value;
newContext = result.context;
} else {
v = null;
ConfigValue fallback = context.options().getResolver().lookup(expr.path().render());
v = (AbstractConfigValue) fallback;
}
} catch (NotPossibleToResolve e) {
if (ConfigImpl.traceSubstitutionsEnabled())

View File

@ -56,15 +56,28 @@ final class PropertiesParser {
static AbstractConfigObject fromProperties(ConfigOrigin origin,
Properties props) {
return fromEntrySet(origin, props.entrySet());
}
private static <K, V> AbstractConfigObject fromEntrySet(ConfigOrigin origin, Set<Map.Entry<K, V>> entries) {
final Map<Path, Object> pathMap = getPathMap(entries);
return fromPathMap(origin, pathMap, true /* from properties */);
}
private static <K, V> Map<Path, Object> getPathMap(Set<Map.Entry<K, V>> entries) {
Map<Path, Object> pathMap = new HashMap<Path, Object>();
for (Map.Entry<Object, Object> entry : props.entrySet()) {
for (Map.Entry<K, V> entry : entries) {
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 */);
return pathMap;
}
static AbstractConfigObject fromStringMap(ConfigOrigin origin, Map<String, String> stringMap) {
return fromEntrySet(origin, stringMap.entrySet());
}
static AbstractConfigObject fromPathMap(ConfigOrigin origin,

View File

@ -3,8 +3,10 @@
*/
package com.typesafe.config.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.Externalizable;
@ -465,19 +467,23 @@ class SerializedConfigValue extends AbstractConfigValue implements Externalizabl
SerializedField code = readCode(in);
if (code == SerializedField.END_MARKER) {
return;
} else if (code == SerializedField.ROOT_VALUE) {
in.readInt(); // discard length
this.value = readValue(in, null /* baseOrigin */);
}
DataInput input = fieldIn(in);
if (code == SerializedField.ROOT_VALUE) {
this.value = readValue(input, null /* baseOrigin */);
} else if (code == SerializedField.ROOT_WAS_CONFIG) {
in.readInt(); // discard length
this.wasConfig = in.readBoolean();
} else {
// ignore unknown field
skipField(in);
this.wasConfig = input.readBoolean();
}
}
}
private DataInput fieldIn(ObjectInput in) throws IOException {
byte[] bytes = new byte[in.readInt()];
in.readFully(bytes);
return new DataInputStream(new ByteArrayInputStream(bytes));
}
private static ConfigException shouldNotBeUsed() {
return new ConfigException.BugOrBroken(SerializedConfigValue.class.getName()
+ " should not exist outside of serialization");

View File

@ -7,7 +7,11 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
@ -322,6 +326,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return Duration.ofNanos(nanos);
}
@Override
public Period getPeriod(String path){
ConfigValue v = find(path, ConfigValueType.STRING);
return parsePeriod((String) v.unwrapped(), v.origin(), path);
}
@Override
public TemporalAmount getTemporal(String path){
try{
return getDuration(path);
} catch (ConfigException.BadValue e){
return getPeriod(path);
}
}
@SuppressWarnings("unchecked")
private <T> List<T> getHomogeneousUnwrappedList(String path,
ConfigValueType expected) {
@ -583,6 +602,90 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return s.substring(i + 1);
}
/**
* Parses a period string. If no units are specified in the string, it is
* assumed to be in days. The returned period is in days.
* The purpose of this function is to implement the period-related methods
* in the ConfigObject interface.
*
* @param input
* the string to parse
* @param originForException
* origin of the value being parsed
* @param pathForException
* path to include in exceptions
* @return duration in days
* @throws ConfigException
* if string is invalid
*/
public static Period parsePeriod(String input,
ConfigOrigin originForException, String pathForException) {
String s = ConfigImplUtil.unicodeTrim(input);
String originalUnitString = getUnits(s);
String unitString = originalUnitString;
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length()
- unitString.length()));
ChronoUnit units;
// this would be caught later anyway, but the error message
// is more helpful if we check it here.
if (numberString.length() == 0)
throw new ConfigException.BadValue(originForException,
pathForException, "No number in period value '" + input
+ "'");
if (unitString.length() > 2 && !unitString.endsWith("s"))
unitString = unitString + "s";
// note that this is deliberately case-sensitive
if (unitString.equals("") || unitString.equals("d") || unitString.equals("days")) {
units = ChronoUnit.DAYS;
} else if (unitString.equals("w") || unitString.equals("weeks")) {
units = ChronoUnit.WEEKS;
} else if (unitString.equals("m") || unitString.equals("mo") || unitString.equals("months")) {
units = ChronoUnit.MONTHS;
} else if (unitString.equals("y") || unitString.equals("years")) {
units = ChronoUnit.YEARS;
} else {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse time unit '"
+ originalUnitString
+ "' (try d, w, mo, y)");
}
try {
return periodOf(Integer.parseInt(numberString), units);
} catch (NumberFormatException e) {
throw new ConfigException.BadValue(originForException,
pathForException, "Could not parse duration number '"
+ numberString + "'");
}
}
private static Period periodOf(int n, ChronoUnit unit){
if(unit.isTimeBased()){
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
}
switch (unit){
case DAYS:
return Period.ofDays(n);
case WEEKS:
return Period.ofWeeks(n);
case MONTHS:
return Period.ofMonths(n);
case YEARS:
return Period.ofYears(n);
default:
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
}
}
/**
* Parses a duration string. If no units are specified in the string, it is
* assumed to be in milliseconds. The returned duration is in nanoseconds.

View File

@ -0,0 +1,24 @@
package beanconfig;
public class DifferentFieldNameFromAccessorsConfig {
private String customStringField;
private Long number;
public String getStringField() {
return customStringField;
}
public void setStringField(String stringField) {
this.customStringField = stringField;
}
public Long getNumber() {
return number;
}
public void setNumber(Long number) {
this.number = number;
}
}

View File

@ -0,0 +1,139 @@
package beanconfig;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigMemorySize;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import java.time.Duration;
import java.util.Set;
public class SetsConfig {
Set<Integer> empty;
Set<Integer> ofInt;
Set<String> ofString;
Set<Double> ofDouble;
Set<Long> ofLong;
Set<Object> ofNull;
Set<Boolean> ofBoolean;
Set<Object> ofObject;
Set<Config> ofConfig;
Set<ConfigObject> ofConfigObject;
Set<ConfigValue> ofConfigValue;
Set<Duration> ofDuration;
Set<ConfigMemorySize> ofMemorySize;
Set<StringsConfig> ofStringBean;
public Set<Integer> getEmpty() {
return empty;
}
public void setEmpty(Set<Integer> empty) {
this.empty = empty;
}
public Set<Integer> getOfInt() {
return ofInt;
}
public void setOfInt(Set<Integer> ofInt) {
this.ofInt = ofInt;
}
public Set<String> getOfString() {
return ofString;
}
public void setOfString(Set<String> ofString) {
this.ofString = ofString;
}
public Set<Double> getOfDouble() {
return ofDouble;
}
public void setOfDouble(Set<Double> ofDouble) {
this.ofDouble = ofDouble;
}
public Set<Object> getOfNull() {
return ofNull;
}
public void setOfNull(Set<Object> ofNull) {
this.ofNull = ofNull;
}
public Set<Boolean> getOfBoolean() {
return ofBoolean;
}
public void setOfBoolean(Set<Boolean> ofBoolean) {
this.ofBoolean = ofBoolean;
}
public Set<Object> getOfObject() {
return ofObject;
}
public void setOfObject(Set<Object> ofObject) {
this.ofObject = ofObject;
}
public Set<Long> getOfLong() {
return ofLong;
}
public void setOfLong(Set<Long> ofLong) {
this.ofLong = ofLong;
}
public Set<Config> getOfConfig() {
return ofConfig;
}
public void setOfConfig(Set<Config> ofConfig) {
this.ofConfig = ofConfig;
}
public Set<ConfigObject> getOfConfigObject() {
return ofConfigObject;
}
public void setOfConfigObject(Set<ConfigObject> ofConfigObject) {
this.ofConfigObject = ofConfigObject;
}
public Set<ConfigValue> getOfConfigValue() {
return ofConfigValue;
}
public void setOfConfigValue(Set<ConfigValue> ofConfigValue) {
this.ofConfigValue = ofConfigValue;
}
public Set<Duration> getOfDuration() {
return ofDuration;
}
public void setOfDuration(Set<Duration> ofDuration) {
this.ofDuration = ofDuration;
}
public Set<ConfigMemorySize> getOfMemorySize() {
return ofMemorySize;
}
public void setOfMemorySize(Set<ConfigMemorySize> ofMemorySize) {
this.ofMemorySize = ofMemorySize;
}
public Set<StringsConfig> getOfStringBean() {
return ofStringBean;
}
public void setOfStringBean(Set<StringsConfig> ofStringBean) {
this.ofStringBean = ofStringBean;
}
}

View File

@ -101,5 +101,31 @@
"valueObject": {
"mandatoryValue": "notNull"
}
},
"sets" : {
"empty" : [],
"ofInt" : [1, 2, 3, 2, 3],
"ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ],
"of-double" : [3.14, 4.14, 4.14, 5.14],
"of-long" : { "1" : 32, "2" : 42, "3" : 52 }, // object-to-list conversion
"ofNull" : [null, null, null],
"ofBoolean" : [true, false, false],
"ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}],
"ofObject" : [${numbers}, ${booleans}, ${strings}],
"ofConfig" : [${numbers}, ${booleans}, ${strings}],
"ofConfigObject" : [${numbers}, ${booleans}, ${strings}],
"ofConfigValue" : [1, 2, "a"],
"ofDuration" : [1, 2h, 3 days],
"ofMemorySize" : [1024, 1M, 1G],
"ofStringBean" : [
{
abcd : "testAbcdOne"
yes : "testYesOne"
},
{
abcd : "testAbcdTwo"
yes : "testYesTwo"
}
]
}
}

View File

@ -65,6 +65,14 @@
"minusLargeNanos" : -4878955355435272204ns
},
"periods" : {
"day" : 1d,
"dayAsNumber": 2,
"week": 3 weeks,
"month": 5 mo,
"year": 8y
},
"memsizes" : {
"meg" : 1M,
"megsList" : [1M, 1024K, 1048576],

View File

@ -132,6 +132,39 @@ class ConfigBeanFactoryTest extends TestUtils {
assertEquals(List(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
}
@Test
def testCreateSet() {
val beanConfig: SetsConfig = ConfigBeanFactory.create(loadConfig().getConfig("sets"), classOf[SetsConfig])
assertNotNull(beanConfig)
assertEquals(Set().asJava, beanConfig.getEmpty)
assertEquals(Set(1, 2, 3).asJava, beanConfig.getOfInt)
assertEquals(Set(32L, 42L, 52L).asJava, beanConfig.getOfLong)
assertEquals(Set("a", "b", "c").asJava, beanConfig.getOfString)
assertEquals(3, beanConfig.getOfObject.size)
assertEquals(3, beanConfig.getOfDouble.size)
assertEquals(3, beanConfig.getOfConfig.size)
assertTrue(beanConfig.getOfConfig.iterator().next().isInstanceOf[Config])
assertEquals(3, beanConfig.getOfConfigObject.size)
assertTrue(beanConfig.getOfConfigObject.iterator().next().isInstanceOf[ConfigObject])
assertEquals(Set(intValue(1), intValue(2), stringValue("a")),
beanConfig.getOfConfigValue.asScala)
assertEquals(Set(Duration.ofMillis(1), Duration.ofHours(2), Duration.ofDays(3)),
beanConfig.getOfDuration.asScala)
assertEquals(Set(ConfigMemorySize.ofBytes(1024),
ConfigMemorySize.ofBytes(1048576),
ConfigMemorySize.ofBytes(1073741824)),
beanConfig.getOfMemorySize.asScala)
val stringsConfigOne = new StringsConfig();
stringsConfigOne.setAbcd("testAbcdOne")
stringsConfigOne.setYes("testYesOne")
val stringsConfigTwo = new StringsConfig();
stringsConfigTwo.setAbcd("testAbcdTwo")
stringsConfigTwo.setYes("testYesTwo")
assertEquals(Set(stringsConfigOne, stringsConfigTwo).asJava, beanConfig.getOfStringBean)
}
@Test
def testCreateDuration() {
val beanConfig: DurationsConfig = ConfigBeanFactory.create(loadConfig().getConfig("durations"), classOf[DurationsConfig])
@ -237,6 +270,14 @@ class ConfigBeanFactoryTest extends TestUtils {
assertTrue("error about the right property", e.getMessage.contains("'map'"))
}
@Test
def testDifferentFieldNameFromAccessors(): Unit = {
val e = intercept[ConfigException.ValidationFailed] {
ConfigBeanFactory.create(ConfigFactory.empty(), classOf[DifferentFieldNameFromAccessorsConfig])
}
assertTrue("only one missing value error", e.getMessage.contains("No setting"))
}
private def loadConfig(): Config = {
val configIs: InputStream = this.getClass().getClassLoader().getResourceAsStream("beanconfig/beanconfig01.conf")
try {

View File

@ -292,7 +292,7 @@ class ConfigDocumentTest extends TestUtils {
@Test
def configDocumentFileParse {
val configDocument = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf"))
val fileReader = new BufferedReader(new FileReader("config/src/test/resources/test03.conf"))
val fileReader = new BufferedReader(new FileReader("src/test/resources/test03.conf"))
var line = fileReader.readLine()
val sb = new StringBuilder()
while (line != null) {

View File

@ -10,6 +10,7 @@ import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigResolveOptions
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import scala.collection.JavaConverters._
class ConfigSubstitutionTest extends TestUtils {
@ -723,6 +724,35 @@ class ConfigSubstitutionTest extends TestUtils {
checkNotSerializable(substComplexObject)
}
@Test
def resolveListFromSystemProps() {
val props = parseObject(
"""
|"a": ${testList}
""".stripMargin)
System.setProperty("testList.0", "0")
System.setProperty("testList.1", "1")
ConfigImpl.reloadSystemPropertiesConfig()
val resolved = resolve(ConfigFactory.systemProperties().withFallback(props).root.asInstanceOf[AbstractConfigObject])
assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala)
}
@Test
def resolveListFromEnvVars() {
val props = parseObject(
"""
|"a": ${testList}
""".stripMargin)
//"testList.0" and "testList.1" are defined as envVars in build.sbt
val resolved = resolve(props)
assertEquals(List("0", "1"), resolved.getList("a").unwrapped().asScala)
}
// this is a weird test, it used to test fallback to system props which made more sense.
// Now it just tests that if you override with system props, you can use system props
// in substitutions.

View File

@ -3,13 +3,16 @@
*/
package com.typesafe.config.impl
import java.time.temporal.{ ChronoUnit, TemporalUnit }
import org.junit.Assert._
import org.junit._
import com.typesafe.config._
import java.util.concurrent.TimeUnit
import scala.collection.JavaConverters._
import com.typesafe.config.ConfigResolveOptions
import java.util.concurrent.TimeUnit.{ SECONDS, NANOSECONDS, MICROSECONDS, MILLISECONDS, MINUTES, DAYS, HOURS }
import java.util.concurrent.TimeUnit.{ DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS }
class ConfigTest extends TestUtils {
@ -811,6 +814,13 @@ class ConfigTest extends TestUtils {
assertDurationAsTimeUnit(HOURS)
assertDurationAsTimeUnit(DAYS)
// periods
assertEquals(1, conf.getPeriod("periods.day").get(ChronoUnit.DAYS))
assertEquals(2, conf.getPeriod("periods.dayAsNumber").getDays)
assertEquals(3 * 7, conf.getTemporal("periods.week").get(ChronoUnit.DAYS))
assertEquals(5, conf.getTemporal("periods.month").get(ChronoUnit.MONTHS))
assertEquals(8, conf.getTemporal("periods.year").get(ChronoUnit.YEARS))
// should get size in bytes
assertEquals(1024 * 1024L, conf.getBytes("memsizes.meg"))
assertEquals(1024 * 1024L, conf.getBytes("memsizes.megAsNumber"))
@ -1233,4 +1243,70 @@ class ConfigTest extends TestUtils {
val resolved = unresolved.resolveWith(source)
assertEquals(43, resolved.getInt("foo"))
}
/**
* A resolver that replaces paths that start with a particular prefix with
* strings where that prefix has been replaced with another prefix.
*/
class DummyResolver(prefix: String, newPrefix: String, fallback: ConfigResolver) extends ConfigResolver {
override def lookup(path: String): ConfigValue = {
if (path.startsWith(prefix))
ConfigValueFactory.fromAnyRef(newPrefix + path.substring(prefix.length))
else if (fallback != null)
fallback.lookup(path)
else
null
}
override def withFallback(f: ConfigResolver): ConfigResolver = {
if (fallback == null)
new DummyResolver(prefix, newPrefix, f)
else
new DummyResolver(prefix, newPrefix, fallback.withFallback(f))
}
}
private def runFallbackTest(expected: String, source: String,
allowUnresolved: Boolean, resolvers: ConfigResolver*) = {
val unresolved = ConfigFactory.parseString(source)
var options = ConfigResolveOptions.defaults().setAllowUnresolved(allowUnresolved)
for (resolver <- resolvers)
options = options.appendResolver(resolver)
val obj = unresolved.resolve(options).root()
assertEquals(expected, obj.render(ConfigRenderOptions.concise().setJson(false)))
}
@Test
def resolveFallback(): Unit = {
runFallbackTest(
"x=a,y=b",
"x=${a},y=${b}", false,
new DummyResolver("", "", null))
runFallbackTest(
"x=\"a.b.c\",y=\"a.b.d\"",
"x=${a.b.c},y=${a.b.d}", false,
new DummyResolver("", "", null))
runFallbackTest(
"x=${a.b.c},y=${a.b.d}",
"x=${a.b.c},y=${a.b.d}", true,
new DummyResolver("x.", "", null))
runFallbackTest(
"x=${a.b.c},y=\"e.f\"",
"x=${a.b.c},y=${d.e.f}", true,
new DummyResolver("d.", "", null))
runFallbackTest(
"w=\"Y.c.d\",x=${a},y=\"X.b\",z=\"Y.c\"",
"x=${a},y=${a.b},z=${a.b.c},w=${a.b.c.d}", true,
new DummyResolver("a.b.", "Y.", null),
new DummyResolver("a.", "X.", null))
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", true, new DummyResolver("x.", "", null))
val e = intercept[ConfigException.UnresolvedSubstitution] {
runFallbackTest("x=${a.b.c}", "x=${a.b.c}", false, new DummyResolver("x.", "", null))
}
assertTrue(e.getMessage.contains("${a.b.c}"))
}
}

View File

@ -279,6 +279,23 @@ class ConfigValueTest extends TestUtils {
assertTrue(b.root.toConfig eq b)
}
/**
* Reproduces the issue <a href=https://github.com/typesafehub/config/issues/461>#461</a>.
* <p>
* We use a custom de-/serializer that encodes String objects in a JDK-incompatible way. Encoding used here
* is rather simplistic: a long indicating the length in bytes (JDK uses a variable length integer) followed
* by the string's bytes. Running this test with the original SerializedConfigValue.readExternal()
* implementation results in an EOFException thrown during deserialization.
*/
@Test
def configConfigCustomSerializable() {
val aMap = configMap("a" -> 1, "b" -> 2, "c" -> 3)
val expected = new SimpleConfigObject(fakeOrigin(), aMap).toConfig
val actual = checkSerializableWithCustomSerializer(expected)
assertEquals(expected, actual)
}
@Test
def configListEquality() {
val aScalaSeq = Seq(1, 2, 3) map { intValue(_): AbstractConfigValue }

View File

@ -7,8 +7,7 @@ import org.junit.Assert._
import org.junit._
import scala.collection.JavaConverters._
import com.typesafe.config._
import java.util.Collections
import java.util.TreeSet
import java.util.{ Collections, TimeZone, TreeSet }
import java.io.File
import scala.collection.mutable
import equiv03.SomethingInEquiv03
@ -17,6 +16,15 @@ import java.net.URL
import java.time.Duration
class PublicApiTest extends TestUtils {
@Before
def before(): Unit = {
// TimeZone.getDefault internally invokes System.setProperty("user.timezone", <default time zone>) and it may
// cause flaky tests depending on tests order and jvm options. This method is invoked
// eg. by URLConnection.getContentType (it reads headers and gets default time zone).
TimeZone.getDefault
}
@Test
def basicLoadAndGet() {
val conf = ConfigFactory.load("test01")
@ -1016,8 +1024,8 @@ class PublicApiTest extends TestUtils {
assertTrue("invalidate caches works on changed system props sys", sys2 ne sys3)
assertTrue("invalidate caches works on changed system props conf", conf2 ne conf3)
assertTrue("invalidate caches doesn't change value if no system prop changes sys", sys1 == sys2)
assertTrue("invalidate caches doesn't change value if no system prop changes conf", conf1 == conf2)
assertEquals("invalidate caches doesn't change value if no system prop changes sys", sys1, sys2)
assertEquals("invalidate caches doesn't change value if no system prop changes conf", conf1, conf2)
assertTrue("test system property is set sys", sys3.hasPath("invalidateCachesTest"))
assertTrue("test system property is set conf", conf3.hasPath("invalidateCachesTest"))

View File

@ -16,6 +16,8 @@ import java.io.ObjectOutputStream
import java.io.ByteArrayInputStream
import java.io.ObjectInputStream
import java.io.NotSerializableException
import java.io.OutputStream
import java.io.InputStream
import scala.annotation.tailrec
import java.net.URL
import java.util.Locale
@ -721,7 +723,7 @@ abstract trait TestUtils {
def path(elements: String*) = new Path(elements: _*)
val resourceDir = {
val f = new File("config/src/test/resources")
val f = new File("src/test/resources")
if (!f.exists()) {
val here = new File(".").getAbsolutePath
throw new Exception(s"Tests must be run from the root project directory containing ${f.getPath()}, however the current directory is $here")
@ -871,7 +873,7 @@ abstract trait TestUtils {
}
protected def withScratchDirectory[T](testcase: String)(body: File => T): Unit = {
val target = new File("config/target")
val target = new File("target")
if (!target.isDirectory)
throw new RuntimeException(s"Expecting $target to exist")
val suffix = java.lang.Integer.toHexString(java.util.concurrent.ThreadLocalRandom.current.nextInt)
@ -883,4 +885,32 @@ abstract trait TestUtils {
deleteRecursive(scratch)
}
}
protected def checkSerializableWithCustomSerializer[T: Manifest](o: T): T = {
val byteStream = new ByteArrayOutputStream()
val objectStream = new CustomObjectOutputStream(byteStream)
objectStream.writeObject(o)
objectStream.close()
val inStream = new ByteArrayInputStream(byteStream.toByteArray)
val inObjectStream = new CustomObjectInputStream(inStream)
val copy = inObjectStream.readObject()
inObjectStream.close()
copy.asInstanceOf[T]
}
class CustomObjectOutputStream(out: OutputStream) extends ObjectOutputStream(out) {
override def writeUTF(str: String): Unit = {
val bytes = str.getBytes
writeLong(bytes.length)
write(bytes)
}
}
class CustomObjectInputStream(in: InputStream) extends ObjectInputStream(in) {
override def readUTF(): String = {
val bytes = new Array[Byte](readLong().toByte)
read(bytes)
new String(bytes)
}
}
}

View File

@ -3,6 +3,9 @@
*/
package com.typesafe.config.impl
import java.time.{ LocalDate, Period }
import java.time.temporal.ChronoUnit
import org.junit.Assert._
import org.junit._
import com.typesafe.config._
@ -40,6 +43,33 @@ class UnitParserTest extends TestUtils {
assertTrue(e2.getMessage.contains("duration number"))
}
@Test
def parsePeriod() = {
val oneYears = List(
"1y", "1 y", "1year", "1 years", " 1y ", " 1 y ",
"365", "365d", "365 d", "365 days", " 365 days ", "365day",
"12m", "12mo", "12 m", " 12 mo ", "12 months", "12month")
val epochDate = LocalDate.ofEpochDay(0)
val oneYear = ChronoUnit.DAYS.between(epochDate, epochDate.plus(Period.ofYears(1)))
for (y <- oneYears) {
val period = SimpleConfig.parsePeriod(y, fakeOrigin(), "test")
val dayCount = ChronoUnit.DAYS.between(epochDate, epochDate.plus(period))
assertEquals(oneYear, dayCount)
}
// bad units
val e = intercept[ConfigException.BadValue] {
SimpleConfig.parsePeriod("100 dollars", fakeOrigin(), "test")
}
assertTrue(s"${e.getMessage} was not the expected error message", e.getMessage.contains("time unit"))
// bad number
val e2 = intercept[ConfigException.BadValue] {
SimpleConfig.parsePeriod("1 00 seconds", fakeOrigin(), "test")
}
assertTrue(s"${e2.getMessage} was not the expected error message", e2.getMessage.contains("time unit 'seconds'"))
}
// https://github.com/typesafehub/config/issues/117
// this broke because "1d" is a valid double for parseDouble
@Test

View File

@ -1,119 +0,0 @@
import sbt._
import Keys._
import com.etsy.sbt.checkstyle.CheckstylePlugin.autoImport._
import com.typesafe.sbt.osgi.SbtOsgi
import com.typesafe.sbt.osgi.SbtOsgi.autoImport._
import com.typesafe.sbt.JavaVersionCheckPlugin.autoImport._
object ConfigBuild extends Build {
val unpublished = Seq(
// no artifacts in this project
publishArtifact := false,
// make-pom has a more specific publishArtifact setting already
// so needs specific override
publishArtifact in makePom := false,
// no docs to publish
publishArtifact in packageDoc := false,
// can't seem to get rid of ivy files except by no-op'ing the entire publish task
publish := {},
publishLocal := {}
)
object sonatype extends PublishToSonatype {
def projectUrl = "https://github.com/typesafehub/config"
def developerId = "havocp"
def developerName = "Havoc Pennington"
def developerUrl = "http://ometer.com/"
def scmUrl = "git://github.com/typesafehub/config.git"
}
override val settings = super.settings ++ Seq(isSnapshot <<= isSnapshot or version(_ endsWith "-SNAPSHOT"))
lazy val commonSettings: Seq[Setting[_]] = unpublished ++ Seq(javaVersionPrefix in javaVersionCheck := None)
lazy val rootSettings: Seq[Setting[_]] =
commonSettings ++
Seq(aggregate in doc := false,
doc := (doc in (configLib, Compile)).value,
aggregate in packageDoc := false,
packageDoc := (packageDoc in (configLib, Compile)).value,
aggregate in checkstyle := false,
checkstyle := (checkstyle in (configLib, Compile)).value)
lazy val root = Project(id = "root",
base = file("."),
settings = rootSettings) aggregate(testLib, configLib,
simpleLibScala, simpleAppScala, complexAppScala,
simpleLibJava, simpleAppJava, complexAppJava)
lazy val configLib = Project(id = "config",
base = file("config"),
settings =
sonatype.settings ++
osgiSettings ++
Seq(
OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl"),
publish := sys.error("use publishSigned instead of plain publish"),
publishLocal := sys.error("use publishLocalSigned instead of plain publishLocal")
)).enablePlugins(SbtOsgi) dependsOn testLib % "test->test"
def project(id: String, base: File) = Project(id, base, settings = commonSettings)
lazy val testLib = project("config-test-lib", file("test-lib"))
lazy val simpleLibScala = project("config-simple-lib-scala", file("examples/scala/simple-lib")) dependsOn configLib
lazy val simpleAppScala = project("config-simple-app-scala", file("examples/scala/simple-app")) dependsOn simpleLibScala
lazy val complexAppScala = project("config-complex-app-scala", file("examples/scala/complex-app")) dependsOn simpleLibScala
lazy val simpleLibJava = project("config-simple-lib-java", file("examples/java/simple-lib")) dependsOn configLib
lazy val simpleAppJava = project("config-simple-app-java", file("examples/java/simple-app")) dependsOn simpleLibJava
lazy val complexAppJava = project("config-complex-app-java", file("examples/java/complex-app")) dependsOn simpleLibJava
}
// from https://raw.github.com/paulp/scala-improving/master/project/PublishToSonatype.scala
abstract class PublishToSonatype {
val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
def projectUrl: String
def developerId: String
def developerName: String
def developerUrl: String
def licenseName = "Apache License, Version 2.0"
def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
def licenseDistribution = "repo"
def scmUrl: String
def scmConnection = "scm:git:" + scmUrl
def generatePomExtra: xml.NodeSeq = {
<url>{ projectUrl }</url>
<licenses>
<license>
<name>{ licenseName }</name>
<url>{ licenseUrl }</url>
<distribution>{ licenseDistribution }</distribution>
</license>
</licenses>
<scm>
<url>{ scmUrl }</url>
<connection>{ scmConnection }</connection>
</scm>
<developers>
<developer>
<id>{ developerId }</id>
<name>{ developerName }</name>
<url>{ developerUrl }</url>
</developer>
</developers>
}
def settings: Seq[Setting[_]] = Seq(
publishMavenStyle := true,
publishTo <<= isSnapshot { (snapshot) => Some(if (snapshot) ossSnapshots else ossStaging) },
publishArtifact in Test := false,
pomIncludeRepository := (_ => false),
pomExtra := generatePomExtra
)
}

View File

@ -0,0 +1,48 @@
import sbt._, Keys._
// from https://raw.github.com/paulp/scala-improving/master/project/PublishToSonatype.scala
abstract class PublishToSonatype {
val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
def projectUrl: String
def developerId: String
def developerName: String
def developerUrl: String
def licenseName = "Apache License, Version 2.0"
def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
def licenseDistribution = "repo"
def scmUrl: String
def scmConnection = "scm:git:" + scmUrl
def generatePomExtra: xml.NodeSeq = {
<url>{ projectUrl }</url>
<licenses>
<license>
<name>{ licenseName }</name>
<url>{ licenseUrl }</url>
<distribution>{ licenseDistribution }</distribution>
</license>
</licenses>
<scm>
<url>{ scmUrl }</url>
<connection>{ scmConnection }</connection>
</scm>
<developers>
<developer>
<id>{ developerId }</id>
<name>{ developerName }</name>
<url>{ developerUrl }</url>
</developer>
</developers>
}
def settings: Seq[Setting[_]] = Seq(
publishMavenStyle := true,
publishTo := Some(if (isSnapshot.value) ossSnapshots else ossStaging),
publishArtifact in Test := false,
pomIncludeRepository := (_ => false),
pomExtra := generatePomExtra
)
}

View File

@ -1 +1 @@
sbt.version=0.13.11
sbt.version=0.13.16