Enabled environment

This commit is contained in:
thosakwe 2017-06-14 14:48:13 -04:00
parent 9d2a1f246c
commit 66baaff2c4
6 changed files with 121 additions and 32 deletions

View file

@ -1,13 +1,13 @@
# Angel Configuration # Angel Configuration
![version 1.0.3](https://img.shields.io/badge/version-1.0.3-brightgreen.svg) [![Pub](https://img.shields.io/pub/v/angel_configuration.svg)](https://pub.dartlang.org/packages/angel_configuration)
![build status](https://travis-ci.org/angel-dart/configuration.svg) [![build status](https://travis-ci.org/angel-dart/configuration.svg)](https://travis-ci.org/angel-dart/configuration.svg)
Isomorphic YAML configuration loader for Angel. Automatic YAML configuration loader for Angel.
# About # About
Any web app needs different configuration for development and production. This plugin will search Any web app needs different configuration for development and production. This plugin will search
for a `config/default.yaml` file. If it is found, configuration from it is loaded into `angel.properties`. for a `config/default.yaml` file. If it is found, configuration from it is loaded into `app.properties`.
Then, it will look for a `config/$ANGEL_ENV` file. (i.e. config/development.yaml). If this found, all of its Then, it will look for a `config/$ANGEL_ENV` file. (i.e. config/development.yaml). If this found, all of its
configuration be loaded, and will override anything loaded from the `default.yaml` file. This allows for your configuration be loaded, and will override anything loaded from the `default.yaml` file. This allows for your
app to work under different conditions without you re-coding anything. :) app to work under different conditions without you re-coding anything. :)
@ -22,7 +22,37 @@ dependencies:
# Usage # Usage
**Example Configuration**
```yaml
# Define normal YAML objects
some_key: foo
this_is_a_map:
a_string: "string"
another_string: "string"
```
You can also load configuration from the environment:
```yaml
# Loaded from the environment
system_path: $PATH
```
If a `.env` file is present in your configuration directory, then it will be loaded before
applying YAML configuration.
**Server-side** **Server-side**
Call `loadConfigurationFile()`. The loaded properties will be available in your application's
`properties` map, which means you can access them like normal instance members.
```dart
main() {
print(app.foo == app.properties['foo']); // true
}
```
An instance of `Configuration` will also be injected to your application, and it works
the same way:
```dart ```dart
import 'dart:io'; import 'dart:io';
@ -41,7 +71,7 @@ main() async {
`loadConfigurationFile` also accepts a `sourceDirectory` or `overrideEnvironmentName` parameter. `loadConfigurationFile` also accepts a `sourceDirectory` or `overrideEnvironmentName` parameter.
The former will allow you to search in a directory other than `config`, and the latter lets you The former will allow you to search in a directory other than `config`, and the latter lets you
override `$ANGEL_ENV` by specifying a specific configuration name to look for (i.e. 'production'). override `$ANGEL_ENV` by specifying a specific configuration name to look for (i.e. `production`).
**In the Browser** **In the Browser**

View file

@ -2,14 +2,16 @@ library angel_configuration;
import 'dart:io'; import 'dart:io';
import 'package:angel_framework/angel_framework.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_route/src/extensible.dart'; import 'package:dotenv/dotenv.dart' as dotenv;
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
final RegExp _equ = new RegExp(r'=$'); final RegExp _equ = new RegExp(r'=$');
final RegExp _sym = new RegExp(r'Symbol\("([^"]+)"\)'); final RegExp _sym = new RegExp(r'Symbol\("([^"]+)"\)');
/// A proxy object that encapsulates a server's configuration.
@proxy @proxy
class Configuration { class Configuration {
/// The [Angel] instance that loaded this configuration.
final Angel app; final Angel app;
Configuration(this.app); Configuration(this.app);
@ -21,8 +23,8 @@ class Configuration {
String name = _sym.firstMatch(invocation.memberName.toString()).group(1); String name = _sym.firstMatch(invocation.memberName.toString()).group(1);
if (invocation.isMethod) { if (invocation.isMethod) {
return Function.apply(app.properties[name], invocation.positionalArguments, return Function.apply(app.properties[name],
invocation.namedArguments); invocation.positionalArguments, invocation.namedArguments);
} else if (invocation.isGetter) { } else if (invocation.isGetter) {
return app.properties[name]; return app.properties[name];
} }
@ -32,34 +34,74 @@ class Configuration {
} }
} }
_loadYamlFile(Angel app, File yamlFile) async { _loadYamlFile(Angel app, File yamlFile, Map<String, String> env) async {
if (await yamlFile.exists()) { if (await yamlFile.exists()) {
Map config = loadYaml(await yamlFile.readAsString()); var config = loadYaml(await yamlFile.readAsString());
if (config is! Map) {
stderr.writeln(
'WARNING: The configuration at "${yamlFile.absolute.path}" is not a Map. Refusing to load it.');
return;
}
for (String key in config.keys) { for (String key in config.keys) {
app.properties[key] = config[key]; app.properties[key] = _applyEnv(config[key], env ?? {});
} }
} }
} }
loadConfigurationFile( _applyEnv(var v, Map<String, String> env) {
if (v is String) {
if (v.startsWith(r'$') && v.length > 1) {
var key = v.substring(1);
if (env.containsKey(key))
return env[key];
else {
stderr.writeln(
'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) {
return v.map((x) => _applyEnv(x, env ?? {})).toList();
} else if (v is Map) {
return v.keys
.fold<Map>({}, (out, k) => out..[k] = _applyEnv(v[k], env ?? {}));
} else
return v;
}
/// Dynamically loads application configuration from configuration files.
AngelConfigurer loadConfigurationFile(
{String directoryPath: "./config", String overrideEnvironmentName}) { {String directoryPath: "./config", String overrideEnvironmentName}) {
return (Angel app) async { return (Angel app) async {
Directory sourceDirectory = new Directory(directoryPath); Directory sourceDirectory = new Directory(directoryPath);
String environmentName = Platform.environment['ANGEL_ENV'] ?? 'development'; var env = dotenv.env;
var envFile = new File.fromUri(sourceDirectory.uri.resolve('.env'));
if (await envFile.exists()) {
try {
dotenv.load(envFile.absolute.uri.toFilePath());
} catch (_) {
stderr.writeln(
'WARNING: Found an environment configuration at ${envFile.absolute.path}, but it was invalidly formatted. Refusing to load it.');
}
}
String environmentName = env['ANGEL_ENV'] ?? 'development';
if (overrideEnvironmentName != null) { if (overrideEnvironmentName != null) {
environmentName = overrideEnvironmentName; environmentName = overrideEnvironmentName;
} }
File defaultYaml = new File.fromUri( File defaultYaml =
sourceDirectory.absolute.uri.resolve("default.yaml")); new File.fromUri(sourceDirectory.absolute.uri.resolve("default.yaml"));
await _loadYamlFile(app, defaultYaml); await _loadYamlFile(app, defaultYaml, env);
String configFilePath = "$environmentName.yaml"; String configFilePath = "$environmentName.yaml";
File configFile = new File.fromUri( File configFile =
sourceDirectory.absolute.uri.resolve(configFilePath)); new File.fromUri(sourceDirectory.absolute.uri.resolve(configFilePath));
await _loadYamlFile(app, configFile); await _loadYamlFile(app, configFile, env);
app.container.singleton(new Configuration(app)); app.container.singleton(new Configuration(app));
}; };
} }

View file

@ -1,6 +1,6 @@
name: angel_configuration name: angel_configuration
description: Isomorphic YAML configuration loader for Angel. description: Automatic YAML configuration loader for Angel.
version: 1.0.3 version: 1.0.4
author: Tobe O <thosakwe@gmail.com> author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_configuration homepage: https://github.com/angel-dart/angel_configuration
dependencies: dependencies:
@ -8,6 +8,7 @@ dependencies:
angel_framework: ">=1.0.0-dev < 2.0.0" angel_framework: ">=1.0.0-dev < 2.0.0"
angel_route: ">=1.0.0-dev < 2.0.0" angel_route: ">=1.0.0-dev < 2.0.0"
barback: ">=0.15.2 < 0.16.0" barback: ">=0.15.2 < 0.16.0"
dotenv: ">=0.1.3 <0.2.0"
yaml: ">= 2.1.8 < 2.2.0" yaml: ">= 2.1.8 < 2.2.0"
dev_dependencies: dev_dependencies:
test: ">= 0.12.13 < 0.13.0" test: ">= 0.12.13 < 0.13.0"

View file

@ -5,27 +5,37 @@ import 'transformer.dart' as transformer;
main() async { main() async {
// Note: Set ANGEL_ENV to 'development' // Note: Set ANGEL_ENV to 'development'
var app = new Angel();
Angel angel = new Angel(); await app.configure(
await angel.configure(
loadConfigurationFile(directoryPath: './test/config')); loadConfigurationFile(directoryPath: './test/config'));
test('can load based on ANGEL_ENV', () async { test('can load based on ANGEL_ENV', () async {
expect(angel.properties['hello'], equals('world')); expect(app.properties['hello'], equals('world'));
expect(angel.properties['foo']['version'], equals('bar')); expect(app.properties['foo']['version'], equals('bar'));
}); });
test('will load default.yaml if exists', () { test('will load default.yaml if exists', () {
expect(angel.properties["set_via"], equals("default")); expect(app.properties["set_via"], equals("default"));
}); });
test('will load .env if exists', () {
expect(app.properties['artist'], 'Timberlake');
expect(app.properties['angel'], {'framework': 'cool'});
});
test('non-existent environment defaults to null', () {
expect(app.properties.keys, contains('must_be_null'));
expect(app.properties['must_be_null'], null);
});
test('can override ANGEL_ENV', () async { test('can override ANGEL_ENV', () async {
await angel.configure(loadConfigurationFile( await app.configure(loadConfigurationFile(
directoryPath: './test/config', overrideEnvironmentName: 'override')); directoryPath: './test/config', overrideEnvironmentName: 'override'));
expect(angel.properties['hello'], equals('goodbye')); expect(app.properties['hello'], equals('goodbye'));
expect(angel.properties['foo']['version'], equals('baz')); expect(app.properties['foo']['version'], equals('baz'));
}); });
group("transformer", transformer.main); group("transformer", transformer.main);
} }

2
test/config/.env Normal file
View file

@ -0,0 +1,2 @@
ANGEL_FRAMEWORK=cool
JUSTIN=Timberlake

View file

@ -1 +1,5 @@
set_via: default set_via: default
artist: $JUSTIN
angel:
framework: $ANGEL_FRAMEWORK
must_be_null: $NONEXISTENT_KEY_FOO_BAR_BAZ_QUUX