Add dotenv configuration provider
This commit is contained in:
parent
77a4f6064f
commit
a51d5fe00b
3
example/example.dart
Normal file
3
example/example.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
void main() {
|
||||||
|
print('Example');
|
||||||
|
}
|
54
lib/dotenv/dotenv.dart
Normal file
54
lib/dotenv/dotenv.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/// Loads environment variables from a `.env` file.
|
||||||
|
///
|
||||||
|
/// ## usage
|
||||||
|
///
|
||||||
|
/// Once you call [load], the top-level [env] map is available.
|
||||||
|
/// You may wish to prefix the import.
|
||||||
|
///
|
||||||
|
/// import 'package:dotenv/dotenv.dart' show load, env;
|
||||||
|
///
|
||||||
|
/// void main() {
|
||||||
|
/// load();
|
||||||
|
/// var x = env['foo'];
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Verify required variables are present:
|
||||||
|
///
|
||||||
|
/// const _requiredEnvVars = const ['host', 'port'];
|
||||||
|
/// bool get hasEnv => isEveryDefined(_requiredEnvVars);
|
||||||
|
library dotenv;
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
part 'parser.dart';
|
||||||
|
|
||||||
|
var _env = Map<String, String>.from(Platform.environment);
|
||||||
|
|
||||||
|
/// A copy of [Platform.environment](dart:io) including variables loaded at runtime from a file.
|
||||||
|
Map<String, String> get env => _env;
|
||||||
|
|
||||||
|
/// Overwrite [env] with a new writable copy of [Platform.environment](dart:io).
|
||||||
|
Map clean() => _env = Map.from(Platform.environment);
|
||||||
|
|
||||||
|
/// True if all supplied variables have nonempty value; false otherwise.
|
||||||
|
/// Differs from [containsKey](dart:core) by excluding null values.
|
||||||
|
/// Note [load] should be called first.
|
||||||
|
bool isEveryDefined(Iterable<String> vars) =>
|
||||||
|
vars.every((k) => _env[k] != null && (_env[k]?.isNotEmpty ?? false));
|
||||||
|
|
||||||
|
/// Read environment variables from [filename] and add them to [env].
|
||||||
|
/// Logs to [stderr] if [filename] does not exist.
|
||||||
|
void load([String filename = '.env', Parser psr = const Parser()]) {
|
||||||
|
var file = File.fromUri(Uri.file(filename));
|
||||||
|
var lines = _verify(file);
|
||||||
|
_env.addAll(psr.parse(lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _verify(File file) {
|
||||||
|
if (file.existsSync()) return file.readAsLinesSync();
|
||||||
|
stderr.writeln('[dotenv] Load failed: file not found: $file');
|
||||||
|
return [];
|
||||||
|
}
|
92
lib/dotenv/parser.dart
Normal file
92
lib/dotenv/parser.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
part of dotenv;
|
||||||
|
|
||||||
|
/// Creates key-value pairs from strings formatted as environment
|
||||||
|
/// variable definitions.
|
||||||
|
class Parser {
|
||||||
|
static const _singleQuot = "'";
|
||||||
|
static const _keyword = 'export';
|
||||||
|
|
||||||
|
static final _comment = RegExp(r'''#.*(?:[^'"])$''');
|
||||||
|
static final _surroundQuotes = RegExp(r'''^(['"])(.*)\1$''');
|
||||||
|
static final _bashVar = RegExp(r'(?:\\)?(\$)(?:{)?([a-zA-Z_][\w]*)+(?:})?');
|
||||||
|
|
||||||
|
/// [Parser] methods are pure functions.
|
||||||
|
const Parser();
|
||||||
|
|
||||||
|
/// Creates a [Map](dart:core) suitable for merging into [Platform.environment](dart:io).
|
||||||
|
/// Duplicate keys are silently discarded.
|
||||||
|
Map<String, String> parse(Iterable<String> lines) {
|
||||||
|
var out = <String, String>{};
|
||||||
|
lines.forEach((line) {
|
||||||
|
var kv = parseOne(line, env: out);
|
||||||
|
if (kv.isEmpty) return;
|
||||||
|
out.putIfAbsent(kv.keys.single, () => kv.values.single);
|
||||||
|
});
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a single line into a key-value pair.
|
||||||
|
@visibleForTesting
|
||||||
|
Map<String, String> parseOne(String line,
|
||||||
|
{Map<String, String> env: const {}}) {
|
||||||
|
var stripped = strip(line);
|
||||||
|
if (!_isValid(stripped)) return {};
|
||||||
|
|
||||||
|
var idx = stripped.indexOf('=');
|
||||||
|
var lhs = stripped.substring(0, idx);
|
||||||
|
var k = swallow(lhs);
|
||||||
|
if (k.isEmpty) return {};
|
||||||
|
|
||||||
|
var rhs = stripped.substring(idx + 1, stripped.length).trim();
|
||||||
|
var quotChar = surroundingQuote(rhs);
|
||||||
|
var v = unquote(rhs);
|
||||||
|
|
||||||
|
if (quotChar == _singleQuot) {
|
||||||
|
return {k: v};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {k: interpolate(v, env)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Substitutes $bash_vars in [val] with values from [env].
|
||||||
|
@visibleForTesting
|
||||||
|
String interpolate(String val, Map<String, String> env) =>
|
||||||
|
val.replaceAllMapped(_bashVar, (m) {
|
||||||
|
var k = m.group(2)!;
|
||||||
|
return (!_has(env, k)) ? _tryPlatformEnv(k) ?? '' : env[k] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
/// If [val] is wrapped in single or double quotes, returns the quote character.
|
||||||
|
/// Otherwise, returns the empty string.
|
||||||
|
@visibleForTesting
|
||||||
|
String surroundingQuote(String val) {
|
||||||
|
if (!_surroundQuotes.hasMatch(val)) return '';
|
||||||
|
return _surroundQuotes.firstMatch(val)!.group(1)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes quotes (single or double) surrounding a value.
|
||||||
|
@visibleForTesting
|
||||||
|
String unquote(String val) =>
|
||||||
|
val.replaceFirstMapped(_surroundQuotes, (m) => m[2]!).trim();
|
||||||
|
|
||||||
|
/// Strips comments (trailing or whole-line).
|
||||||
|
@visibleForTesting
|
||||||
|
String strip(String line) => line.replaceAll(_comment, '').trim();
|
||||||
|
|
||||||
|
/// Omits 'export' keyword.
|
||||||
|
@visibleForTesting
|
||||||
|
String swallow(String line) => line.replaceAll(_keyword, '').trim();
|
||||||
|
|
||||||
|
bool _isValid(String s) => s.isNotEmpty && s.contains('=');
|
||||||
|
|
||||||
|
/// [null] is a valid value in a Dart map, but the env var representation is empty string, not the string 'null'
|
||||||
|
bool _has(Map<String, String> map, String key) =>
|
||||||
|
map.containsKey(key) && map[key] != null;
|
||||||
|
|
||||||
|
String? _tryPlatformEnv(String key) {
|
||||||
|
if (!_has(Platform.environment, key)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return Platform.environment[key];
|
||||||
|
}
|
||||||
|
}
|
20
lib/dotenv_configuration_provider.dart
Normal file
20
lib/dotenv_configuration_provider.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:configurable/configuration_provider.dart';
|
||||||
|
import 'package:configurable/dotenv/dotenv.dart' show env;
|
||||||
|
|
||||||
|
class DotenvConfigurationProvider implements ConfigurationProvider {
|
||||||
|
@override
|
||||||
|
bool containsKey(String key) {
|
||||||
|
return env.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? getOrNull(String key, {String? defaultValue}) {
|
||||||
|
var value = env[key];
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -135,7 +135,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.10"
|
version: "0.12.10"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -8,6 +8,7 @@ environment:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
test: ^1.16.8
|
test: ^1.16.8
|
||||||
|
meta: ^1.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pedantic: ^1.9.0
|
pedantic: ^1.9.0
|
||||||
|
@ -10,4 +10,5 @@ void main() {
|
|||||||
expect(value, equals(result));
|
expect(value, equals(result));
|
||||||
expect(value, equals(SystemConfig.getOrNull(key)));
|
expect(value, equals(SystemConfig.getOrNull(key)));
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
20
test/dotenv_test.dart
Normal file
20
test/dotenv_test.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:configurable/dotenv/dotenv.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('just call env', () {
|
||||||
|
load();
|
||||||
|
|
||||||
|
var result = env['app.name'];
|
||||||
|
|
||||||
|
expect('CUBETIQ Solution', equals(result));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('get export variable', () {
|
||||||
|
load();
|
||||||
|
|
||||||
|
var result = env['NAME'];
|
||||||
|
|
||||||
|
expect('Sambo Chea', equals(result));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user