2016-04-22 01:17:31 +00:00
|
|
|
library angel_configuration;
|
|
|
|
|
|
|
|
import 'package:angel_framework/angel_framework.dart';
|
2017-06-14 18:48:13 +00:00
|
|
|
import 'package:dotenv/dotenv.dart' as dotenv;
|
2017-09-25 13:50:56 +00:00
|
|
|
import 'package:file/file.dart';
|
2017-08-16 00:29:13 +00:00
|
|
|
import 'package:merge_map/merge_map.dart';
|
2016-04-22 01:17:31 +00:00
|
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
|
2016-11-23 20:51:20 +00:00
|
|
|
final RegExp _equ = new RegExp(r'=$');
|
|
|
|
final RegExp _sym = new RegExp(r'Symbol\("([^"]+)"\)');
|
|
|
|
|
2017-06-14 18:48:13 +00:00
|
|
|
/// A proxy object that encapsulates a server's configuration.
|
2016-11-23 23:40:42 +00:00
|
|
|
@proxy
|
2016-11-23 20:51:20 +00:00
|
|
|
class Configuration {
|
2017-06-14 18:48:13 +00:00
|
|
|
/// The [Angel] instance that loaded this configuration.
|
2016-11-23 20:51:20 +00:00
|
|
|
final Angel app;
|
|
|
|
Configuration(this.app);
|
|
|
|
|
2017-09-25 13:50:56 +00:00
|
|
|
operator [](key) => app.configuration[key];
|
|
|
|
operator []=(key, value) => app.configuration[key] = value;
|
2016-11-23 20:51:20 +00:00
|
|
|
|
|
|
|
noSuchMethod(Invocation invocation) {
|
|
|
|
if (invocation.memberName != null) {
|
|
|
|
String name = _sym.firstMatch(invocation.memberName.toString()).group(1);
|
|
|
|
|
|
|
|
if (invocation.isMethod) {
|
2017-09-25 13:50:56 +00:00
|
|
|
return Function.apply(app.configuration[name],
|
2017-06-14 18:48:13 +00:00
|
|
|
invocation.positionalArguments, invocation.namedArguments);
|
2016-11-23 20:51:20 +00:00
|
|
|
} else if (invocation.isGetter) {
|
2017-09-25 13:50:56 +00:00
|
|
|
return app.configuration[name];
|
2016-11-23 20:51:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
super.noSuchMethod(invocation);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-14 18:48:13 +00:00
|
|
|
_loadYamlFile(Angel app, File yamlFile, Map<String, String> env) async {
|
2016-05-02 23:35:21 +00:00
|
|
|
if (await yamlFile.exists()) {
|
2017-06-14 18:48:13 +00:00
|
|
|
var config = loadYaml(await yamlFile.readAsString());
|
2017-09-25 13:50:56 +00:00
|
|
|
|
2017-06-14 18:48:13 +00:00
|
|
|
if (config is! Map) {
|
2017-09-25 13:50:56 +00:00
|
|
|
app.logger?.warning(
|
2017-06-14 18:48:13 +00:00
|
|
|
'WARNING: The configuration at "${yamlFile.absolute.path}" is not a Map. Refusing to load it.');
|
|
|
|
return;
|
|
|
|
}
|
2017-08-16 00:29:13 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> out = {};
|
|
|
|
|
2016-04-22 01:17:31 +00:00
|
|
|
for (String key in config.keys) {
|
2017-09-25 13:50:56 +00:00
|
|
|
out[key] = _applyEnv(config[key], env ?? {}, app);
|
2016-04-22 01:17:31 +00:00
|
|
|
}
|
2017-08-16 00:29:13 +00:00
|
|
|
|
2017-09-25 13:50:56 +00:00
|
|
|
app.configuration.addAll(mergeMap(
|
2017-08-16 00:29:13 +00:00
|
|
|
[
|
2017-09-25 13:50:56 +00:00
|
|
|
app.configuration,
|
2017-08-16 00:29:13 +00:00
|
|
|
out,
|
|
|
|
],
|
|
|
|
acceptNull: true,
|
|
|
|
));
|
2016-04-22 01:17:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-25 13:50:56 +00:00
|
|
|
_applyEnv(var v, Map<String, String> env, Angel app) {
|
2017-06-14 18:48:13 +00:00
|
|
|
if (v is String) {
|
|
|
|
if (v.startsWith(r'$') && v.length > 1) {
|
|
|
|
var key = v.substring(1);
|
|
|
|
if (env.containsKey(key))
|
|
|
|
return env[key];
|
|
|
|
else {
|
2017-09-25 13:50:56 +00:00
|
|
|
app.logger?.warning(
|
2017-06-14 18:48:13 +00:00
|
|
|
'Your configuration calls for loading the value of "$key" from the system environment, but it is not defined. Defaulting to `null`.');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
return v;
|
|
|
|
} else if (v is Iterable) {
|
2017-09-25 13:50:56 +00:00
|
|
|
return v.map((x) => _applyEnv(x, env ?? {}, app)).toList();
|
2017-06-14 18:48:13 +00:00
|
|
|
} else if (v is Map) {
|
|
|
|
return v.keys
|
2017-09-25 13:50:56 +00:00
|
|
|
.fold<Map>({}, (out, k) => out..[k] = _applyEnv(v[k], env ?? {}, app));
|
2017-06-14 18:48:13 +00:00
|
|
|
} else
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Dynamically loads application configuration from configuration files.
|
2017-08-16 00:29:13 +00:00
|
|
|
///
|
|
|
|
/// You can modify which [directoryPath] to search in, or explicitly
|
|
|
|
/// load from a [overrideEnvironmentName].
|
|
|
|
///
|
|
|
|
/// You can also specify a custom [envPath] to load system configuration from.
|
2017-09-25 13:50:56 +00:00
|
|
|
AngelConfigurer configuration(
|
|
|
|
FileSystem fileSystem,
|
2017-08-16 00:29:13 +00:00
|
|
|
{String directoryPath: "./config",
|
|
|
|
String overrideEnvironmentName,
|
|
|
|
String envPath}) {
|
2016-05-02 23:35:21 +00:00
|
|
|
return (Angel app) async {
|
2017-09-25 13:50:56 +00:00
|
|
|
Directory sourceDirectory = fileSystem.directory(directoryPath);
|
2017-06-14 18:48:13 +00:00
|
|
|
var env = dotenv.env;
|
2017-09-25 13:50:56 +00:00
|
|
|
var envFile = sourceDirectory.childFile(envPath ?? '.env');
|
2017-06-14 18:48:13 +00:00
|
|
|
|
|
|
|
if (await envFile.exists()) {
|
|
|
|
try {
|
|
|
|
dotenv.load(envFile.absolute.uri.toFilePath());
|
|
|
|
} catch (_) {
|
2017-09-25 13:50:56 +00:00
|
|
|
app.logger?.warning(
|
2017-06-14 18:48:13 +00:00
|
|
|
'WARNING: Found an environment configuration at ${envFile.absolute.path}, but it was invalidly formatted. Refusing to load it.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String environmentName = env['ANGEL_ENV'] ?? 'development';
|
2016-04-22 01:17:31 +00:00
|
|
|
|
|
|
|
if (overrideEnvironmentName != null) {
|
|
|
|
environmentName = overrideEnvironmentName;
|
|
|
|
}
|
|
|
|
|
2017-09-25 13:50:56 +00:00
|
|
|
var defaultYaml = sourceDirectory.childFile('default.yaml');
|
2017-06-14 18:48:13 +00:00
|
|
|
await _loadYamlFile(app, defaultYaml, env);
|
2016-04-22 01:17:31 +00:00
|
|
|
|
|
|
|
String configFilePath = "$environmentName.yaml";
|
2017-09-25 13:50:56 +00:00
|
|
|
var configFile = sourceDirectory.childFile(configFilePath);
|
2016-04-22 01:17:31 +00:00
|
|
|
|
2017-06-14 18:48:13 +00:00
|
|
|
await _loadYamlFile(app, configFile, env);
|
2016-11-28 02:03:05 +00:00
|
|
|
app.container.singleton(new Configuration(app));
|
2016-04-22 01:17:31 +00:00
|
|
|
};
|
2017-06-14 18:48:13 +00:00
|
|
|
}
|