2016-04-22 01:17:31 +00:00
|
|
|
library angel_configuration;
|
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
import 'dart:async';
|
|
|
|
|
2016-04-22 01:17:31 +00:00
|
|
|
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';
|
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
Future<void> _loadYamlFile(Map map, File yamlFile, Map<String, String> env,
|
|
|
|
void Function(String msg) warn) 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) {
|
2018-12-31 15:41:56 +00:00
|
|
|
warn(
|
|
|
|
'The configuration at "${yamlFile.absolute.path}" is not a Map. Refusing to load it.');
|
2017-06-14 18:48:13 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-08-16 00:29:13 +00:00
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
var out = {};
|
2017-08-16 00:29:13 +00:00
|
|
|
|
2021-04-10 11:23:57 +00:00
|
|
|
var configMap = Map.of(config);
|
2020-05-04 15:59:33 +00:00
|
|
|
|
|
|
|
// Check for _include
|
|
|
|
if (configMap.containsKey('_include')) {
|
|
|
|
var include = configMap.remove('_include');
|
|
|
|
var includeList = include is Iterable ? include.toList() : [include];
|
|
|
|
for (var inc in includeList) {
|
|
|
|
if (inc is! String) {
|
|
|
|
warn('Included item $inc is not a String.');
|
|
|
|
} else {
|
|
|
|
var p = yamlFile.fileSystem.path;
|
|
|
|
var includeFilePath = p.join(yamlFile.parent.path, inc);
|
|
|
|
var includeFile = yamlFile.fileSystem.file(includeFilePath);
|
|
|
|
if (!await includeFile.exists()) {
|
|
|
|
warn(
|
|
|
|
'Included configuration file "$includeFilePath" does not exist.');
|
|
|
|
} else {
|
|
|
|
await _loadYamlFile(out, includeFile, env, warn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 11:23:57 +00:00
|
|
|
for (var key in configMap.keys as Iterable<String>) {
|
|
|
|
out[key] = _applyEnv(configMap[key], env, warn);
|
2016-04-22 01:17:31 +00:00
|
|
|
}
|
2017-08-16 00:29:13 +00:00
|
|
|
|
2018-12-31 15:41:56 +00:00
|
|
|
map.addAll(mergeMap(
|
2017-08-16 00:29:13 +00:00
|
|
|
[
|
2018-12-31 15:41:56 +00:00
|
|
|
map,
|
2017-08-16 00:29:13 +00:00
|
|
|
out,
|
|
|
|
],
|
|
|
|
acceptNull: true,
|
|
|
|
));
|
2016-04-22 01:17:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 11:23:57 +00:00
|
|
|
Object? _applyEnv(
|
2020-05-03 15:24:22 +00:00
|
|
|
var v, Map<String, String> env, void Function(String msg) warn) {
|
2017-06-14 18:48:13 +00:00
|
|
|
if (v is String) {
|
|
|
|
if (v.startsWith(r'$') && v.length > 1) {
|
|
|
|
var key = v.substring(1);
|
2020-05-03 15:24:22 +00:00
|
|
|
if (env.containsKey(key)) {
|
2017-06-14 18:48:13 +00:00
|
|
|
return env[key];
|
2020-05-03 15:24:22 +00:00
|
|
|
} else {
|
2018-12-31 15:41:56 +00:00
|
|
|
warn(
|
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;
|
|
|
|
}
|
2020-05-03 15:24:22 +00:00
|
|
|
} else {
|
2017-06-14 18:48:13 +00:00
|
|
|
return v;
|
2020-05-03 15:24:22 +00:00
|
|
|
}
|
2017-06-14 18:48:13 +00:00
|
|
|
} else if (v is Iterable) {
|
2021-04-10 11:23:57 +00:00
|
|
|
return v.map((x) => _applyEnv(x, env, warn)).toList();
|
2017-06-14 18:48:13 +00:00
|
|
|
} else if (v is Map) {
|
|
|
|
return v.keys
|
2021-04-10 11:23:57 +00:00
|
|
|
.fold<Map>({}, (out, k) => out..[k] = _applyEnv(v[k], env, warn));
|
2020-05-03 15:24:22 +00:00
|
|
|
} else {
|
2017-06-14 18:48:13 +00:00
|
|
|
return v;
|
2020-05-03 15:24:22 +00:00
|
|
|
}
|
2017-06-14 18:48:13 +00:00
|
|
|
}
|
|
|
|
|
2018-12-31 15:41:56 +00:00
|
|
|
/// Loads [configuration], and returns a [Map].
|
|
|
|
///
|
|
|
|
/// You can override [onWarning]; otherwise, configuration errors will throw.
|
|
|
|
Future<Map> loadStandaloneConfiguration(FileSystem fileSystem,
|
2020-05-03 15:24:22 +00:00
|
|
|
{String directoryPath = './config',
|
2021-04-10 11:23:57 +00:00
|
|
|
String? overrideEnvironmentName,
|
|
|
|
String? envPath,
|
|
|
|
void Function(String message)? onWarning}) async {
|
2020-05-03 15:24:22 +00:00
|
|
|
var sourceDirectory = fileSystem.directory(directoryPath);
|
2018-12-31 15:41:56 +00:00
|
|
|
var env = dotenv.env;
|
|
|
|
var envFile = sourceDirectory.childFile(envPath ?? '.env');
|
|
|
|
|
|
|
|
if (await envFile.exists()) {
|
|
|
|
dotenv.load(envFile.absolute.uri.toFilePath());
|
|
|
|
}
|
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
var environmentName = env['ANGEL_ENV'] ?? 'development';
|
2018-12-31 15:41:56 +00:00
|
|
|
|
|
|
|
if (overrideEnvironmentName != null) {
|
|
|
|
environmentName = overrideEnvironmentName;
|
|
|
|
}
|
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
onWarning ??= (String message) => throw StateError(message);
|
2018-12-31 15:41:56 +00:00
|
|
|
var out = {};
|
|
|
|
|
|
|
|
var defaultYaml = sourceDirectory.childFile('default.yaml');
|
|
|
|
await _loadYamlFile(out, defaultYaml, env, onWarning);
|
|
|
|
|
2020-05-03 15:24:22 +00:00
|
|
|
var configFilePath = '$environmentName.yaml';
|
2018-12-31 15:41:56 +00:00
|
|
|
var configFile = sourceDirectory.childFile(configFilePath);
|
|
|
|
|
|
|
|
await _loadYamlFile(out, configFile, env, onWarning);
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2017-06-14 18:48:13 +00:00
|
|
|
/// 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.
|
2018-07-12 16:40:54 +00:00
|
|
|
AngelConfigurer configuration(FileSystem fileSystem,
|
2020-05-03 15:24:22 +00:00
|
|
|
{String directoryPath = './config',
|
2021-04-10 11:23:57 +00:00
|
|
|
String? overrideEnvironmentName,
|
|
|
|
String? envPath}) {
|
2016-05-02 23:35:21 +00:00
|
|
|
return (Angel app) async {
|
2020-05-04 15:59:33 +00:00
|
|
|
var config = await loadStandaloneConfiguration(
|
|
|
|
fileSystem,
|
|
|
|
directoryPath: directoryPath,
|
|
|
|
overrideEnvironmentName: overrideEnvironmentName,
|
|
|
|
envPath: envPath,
|
|
|
|
onWarning: app.logger == null
|
|
|
|
? null
|
|
|
|
: (msg) => app.logger?.warning('WARNING: $msg'),
|
|
|
|
);
|
|
|
|
app.configuration.addAll(mergeMap(
|
|
|
|
[
|
|
|
|
app.configuration,
|
|
|
|
config,
|
|
|
|
],
|
|
|
|
acceptNull: true,
|
|
|
|
));
|
2016-04-22 01:17:31 +00:00
|
|
|
};
|
2017-06-14 18:48:13 +00:00
|
|
|
}
|