From 994780638a62b127fec7dd43c9125abea13536c4 Mon Sep 17 00:00:00 2001 From: Eric Ellingson Date: Sun, 8 Aug 2021 19:09:31 -0700 Subject: [PATCH] Allow overriding with other env files renaming and additional readme revert pubspec.lock changes increment minor version level --- README.md | 24 ++++++++++++++++-- lib/src/dotenv.dart | 58 ++++++++++++++++++++++++++++++++++++------- pubspec.yaml | 2 +- test/.env | 4 ++- test/.env-override | 1 + test/dotenv_test.dart | 43 +++++++++++++++++++++++++++++++- 6 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 test/.env-override diff --git a/README.md b/README.md index e81c1ad..f3508f4 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ ESCAPED_DOLLAR_SIGN='$1000' You can merge a map into the environment on load: ```dart - await DotEnv.load(mergeWith: { "FOO": "foo", "BAR": "bar"}); + await dotenv.load(mergeWith: { "FOO": "foo", "BAR": "bar"}); ``` You can also reference these merged variables within `.env`: @@ -137,13 +137,33 @@ You can also reference these merged variables within `.env`: FOOBAR=$FOO$BAR ``` +## Merge with other env files: + +Useful for defining a base set of values, and overriding a subset based on environment. Env files specified first take precedence. + +```env +# .env +TEST_VALUE=base-value +``` + +```env +# .env-staging +TEST_VALUE=staging-value +``` + +```dart + await dotenv.load(fileName: ".env", overrideWith: [".env-staging"]); + + dotenv.get("TEST_VALUE") // => "staging-value" +``` + ## Usage with Platform Environment The Platform.environment map can be merged into the env: ```dart // For example using Platform.environment that contains a CLIENT_ID entry - await DotEnv.load(mergeWith: Platform.environment); + await dotenv.load(mergeWith: Platform.environment); print(env["CLIENT_ID"]); ``` diff --git a/lib/src/dotenv.dart b/lib/src/dotenv.dart index 866129b..3cdb128 100644 --- a/lib/src/dotenv.dart +++ b/lib/src/dotenv.dart @@ -51,23 +51,50 @@ class DotEnv { /// Loads environment variables from the env file into a map /// Merge with any entries defined in [mergeWith] - Future load( - {String fileName = '.env', Parser parser = const Parser(), Map mergeWith = const {}}) async { + /// [overrideWith] is a list of other env files whose values will override values + /// read from [fileName] + Future load({ + String fileName = '.env', + Parser parser = const Parser(), + Map mergeWith = const {}, + List overrideWith = const [], + }) async { clean(); final linesFromFile = await _getEntriesFromFile(fileName); - final linesFromMergeWith = mergeWith.entries.map((entry) => "${entry.key}=${entry.value}").toList(); - final allLines = linesFromMergeWith..addAll(linesFromFile); + final linesFromOverrides = await _getLinesFromOverride(overrideWith); + + final linesFromMergeWith = mergeWith.entries + .map((entry) => "${entry.key}=${entry.value}") + .toList(); + final allLines = linesFromMergeWith + ..addAll(linesFromOverrides) + ..addAll(linesFromFile); + final envEntries = parser.parse(allLines); _envMap.addAll(envEntries); _isInitialized = true; } - Future testLoad( - {String fileInput = '', Parser parser = const Parser(), Map mergeWith = const {}}) async { + Future testLoad({ + String fileInput = '', + Parser parser = const Parser(), + Map mergeWith = const {}, + List overrideWith = const [], + }) async { clean(); final linesFromFile = fileInput.split('\n'); - final linesFromMergeWith = mergeWith.entries.map((entry) => "${entry.key}=${entry.value}").toList(); - final allLines = linesFromMergeWith..addAll(linesFromFile); + + final linesFromOverrides = overrideWith + .map((String lines) => lines.split("\n")) + .expand((x) => x) + .toList(); + + final linesFromMergeWith = mergeWith.entries + .map((entry) => "${entry.key}=${entry.value}") + .toList(); + final allLines = linesFromMergeWith + ..addAll(linesFromOverrides) + ..addAll(linesFromFile); final envEntries = parser.parse(allLines); _envMap.addAll(envEntries); _isInitialized = true; @@ -76,7 +103,8 @@ class DotEnv { /// 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 vars) => vars.every((k) => _envMap[k]?.isNotEmpty ?? false); + bool isEveryDefined(Iterable vars) => + vars.every((k) => _envMap[k]?.isNotEmpty ?? false); Future> _getEntriesFromFile(String filename) async { try { @@ -90,4 +118,16 @@ class DotEnv { throw FileNotFoundError(); } } + + Future> _getLinesFromOverride(List overrideWith) async { + List overrideLines = []; + + for (int i = 0; i < overrideWith.length; i++) { + final overrideWithFile = overrideWith[i]; + final lines = await _getEntriesFromFile(overrideWithFile); + overrideLines = overrideLines..addAll(lines); + } + + return overrideLines; + } } diff --git a/pubspec.yaml b/pubspec.yaml index 3f7ec14..4ca0736 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_dotenv -version: 5.0.0 +version: 5.1.0 description: Easily configure any flutter application with global variables using a `.env` file. homepage: https://github.com/java-james/flutter_dotenv environment: diff --git a/test/.env b/test/.env index db61d7a..a8d5ddc 100644 --- a/test/.env +++ b/test/.env @@ -28,4 +28,6 @@ RETAIN_TRAILING_SQUOTE=retained' RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}' TRIM_SPACE_FROM_UNQUOTED= some spaced out string USERNAME=therealnerdybeast@example.tld - SPACED_KEY = parsed \ No newline at end of file + SPACED_KEY = parsed + +OVERRIDE_VALUE=not-overridden \ No newline at end of file diff --git a/test/.env-override b/test/.env-override new file mode 100644 index 0000000..f377d50 --- /dev/null +++ b/test/.env-override @@ -0,0 +1 @@ +OVERRIDE_VALUE=overridden \ No newline at end of file diff --git a/test/dotenv_test.dart b/test/dotenv_test.dart index 832ac82..d6e7cdf 100644 --- a/test/dotenv_test.dart +++ b/test/dotenv_test.dart @@ -6,7 +6,9 @@ void main() { group('dotenv', () { setUp(() { print(Directory.current.toString()); - dotenv.testLoad(fileInput: File('test/.env').readAsStringSync()); //, mergeWith: Platform.environment + dotenv.testLoad( + fileInput: File('test/.env') + .readAsStringSync()); //, mergeWith: Platform.environment }); test('able to load .env', () { expect(dotenv.env['FOO'], 'foo'); @@ -35,6 +37,45 @@ void main() { expect(dotenv.env['TRIM_SPACE_FROM_UNQUOTED'], 'some spaced out string'); expect(dotenv.env['USERNAME'], 'therealnerdybeast@example.tld'); expect(dotenv.env['SPACED_KEY'], 'parsed'); + expect(dotenv.env['OVERRIDE_VALUE'], 'not-overridden'); + }); + }); + + group('dotenv with overrides', () { + setUp(() { + print(Directory.current.toString()); + dotenv.testLoad( + fileInput: File('test/.env').readAsStringSync(), + overrideWith: [File("test/.env-override").readAsStringSync()], + ); //, mergeWith: Platform.environment + }); + test('able to load .env', () { + expect(dotenv.env['BAR'], 'bar'); + expect(dotenv.env['FOOBAR'], '\$FOOfoobar'); + expect(dotenv.env['ESCAPED_DOLLAR_SIGN'], '\$1000'); + expect(dotenv.env['ESCAPED_QUOTE'], "'"); + expect(dotenv.env['BASIC'], 'basic'); + expect(dotenv.env['AFTER_LINE'], 'after_line'); + expect(dotenv.env['EMPTY'], ''); + expect(dotenv.env['SINGLE_QUOTES'], 'single_quotes'); + expect(dotenv.env['SINGLE_QUOTES_SPACED'], ' single quotes '); + expect(dotenv.env['DOUBLE_QUOTES'], 'double_quotes'); + expect(dotenv.env['DOUBLE_QUOTES_SPACED'], ' double quotes '); + expect(dotenv.env['EXPAND_NEWLINES'], "expand\nnew\nlines"); + expect(dotenv.env['DONT_EXPAND_UNQUOTED'], 'dontexpand\\nnewlines'); + expect(dotenv.env['DONT_EXPAND_SQUOTED'], 'dontexpand\\nnewlines'); + expect(dotenv.env['COMMENTS'], null); + expect(dotenv.env['EQUAL_SIGNS'], 'equals=='); + expect(dotenv.env['RETAIN_INNER_QUOTES'], '{"foo": "bar"}'); + expect(dotenv.env['RETAIN_LEADING_DQUOTE'], "\"retained"); + expect(dotenv.env['RETAIN_LEADING_SQUOTE'], '\'retained'); + expect(dotenv.env['RETAIN_TRAILING_DQUOTE'], 'retained\"'); + expect(dotenv.env['RETAIN_TRAILING_SQUOTE'], "retained\'"); + expect(dotenv.env['RETAIN_INNER_QUOTES_AS_STRING'], '{"foo": "bar"}'); + expect(dotenv.env['TRIM_SPACE_FROM_UNQUOTED'], 'some spaced out string'); + expect(dotenv.env['USERNAME'], 'therealnerdybeast@example.tld'); + expect(dotenv.env['SPACED_KEY'], 'parsed'); + expect(dotenv.env['OVERRIDE_VALUE'], 'overridden'); }); }); }