diff --git a/README.md b/README.md index a1868e5..ee893be 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,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`: @@ -159,6 +159,26 @@ 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" +``` + ## Using in tests There is a `testLoad` method that can be used to load a static set of variables for testing. @@ -199,7 +219,7 @@ 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 c142945..c3bb458 100644 --- a/lib/src/dotenv.dart +++ b/lib/src/dotenv.dart @@ -113,27 +113,38 @@ class DotEnv { /// Loads environment variables from the env file into a map /// Merge with any entries defined in [mergeWith] + + /// [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 [], bool isOptional = false}) async { clean(); List linesFromFile; + List linesFromOverrides; try { linesFromFile = await _getEntriesFromFile(fileName); + linesFromOverrides = await _getLinesFromOverride(overrideWith); } on FileNotFoundError { if (!isOptional) rethrow; linesFromFile = []; + linesFromOverrides = []; } on EmptyEnvFileError { if (!isOptional) rethrow; linesFromFile = []; + linesFromOverrides = []; } final linesFromMergeWith = mergeWith.entries .map((entry) => "${entry.key}=${entry.value}") .toList(); - final allLines = linesFromMergeWith..addAll(linesFromFile); + final allLines = linesFromMergeWith + ..addAll(linesFromOverrides) + ..addAll(linesFromFile); final envEntries = parser.parse(allLines); _envMap.addAll(envEntries); _isInitialized = true; @@ -141,6 +152,7 @@ class DotEnv { void loadFromString({ String envString = '', + List overrideWith = const [], Parser parser = const Parser(), Map mergeWith = const {}, bool isOptional = false, @@ -150,11 +162,20 @@ class DotEnv { throw EmptyEnvFileError(); } final linesFromFile = envString.split('\n'); + 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(linesFromFile); + + final allLines = linesFromMergeWith + ..addAll(linesFromOverrides) + ..addAll(linesFromFile); + final envEntries = parser.parse(allLines); + _envMap.addAll(envEntries); _isInitialized = true; } @@ -177,4 +198,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/test/.env b/test/.env index 1a8cde2..db6f37d 100644 --- a/test/.env +++ b/test/.env @@ -41,4 +41,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 b83213a..63e7658 100644 --- a/test/dotenv_test.dart +++ b/test/dotenv_test.dart @@ -47,6 +47,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'); }); test( 'when getting a vairable that is not in .env, we should get the fallback we defined',