Skip to content

feature: Allow overriding with other env files #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand All @@ -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.
Expand Down Expand Up @@ -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"]);
```

Expand Down
47 changes: 41 additions & 6 deletions lib/src/dotenv.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,26 @@ 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<void> load(
{String fileName = '.env',
Parser parser = const Parser(),
Map<String, String> mergeWith = const {},
List<String> overrideWith = const [],
bool isOptional = false}) async {
clean();
List<String> linesFromFile;
List<String> linesFromOverrides;
try {
linesFromFile = await _getEntriesFromFile(fileName);
linesFromOverrides = await _getLinesFromOverride(overrideWith);
} on FileNotFoundError {
if (isOptional) {
linesFromFile = [];
linesFromOverrides = [];
} else {
rethrow;
}
Expand All @@ -133,23 +141,38 @@ class DotEnv {
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;
}

void testLoad(
{String fileInput = '',
Parser parser = const Parser(),
Map<String, String> mergeWith = const {}}) {
void testLoad({
String fileInput = '',
Parser parser = const Parser(),
Map<String, String> mergeWith = const {},
List<String> overrideWith = const [],
}) {
clean();
final linesFromFile = fileInput.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;
}
Expand All @@ -172,4 +195,16 @@ class DotEnv {
throw FileNotFoundError();
}
}

Future<List<String>> _getLinesFromOverride(List<String> overrideWith) async {
List<String> overrideLines = [];

for (int i = 0; i < overrideWith.length; i++) {
final overrideWithFile = overrideWith[i];
final lines = await _getEntriesFromFile(overrideWithFile);
overrideLines = overrideLines..addAll(lines);
}

return overrideLines;
}
}
4 changes: 3 additions & 1 deletion test/.env
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ RETAIN_TRAILING_SQUOTE=retained'
RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}'
TRIM_SPACE_FROM_UNQUOTED= some spaced out string
[email protected]
SPACED_KEY = parsed
SPACED_KEY = parsed

OVERRIDE_VALUE=not-overridden
1 change: 1 addition & 0 deletions test/.env-override
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OVERRIDE_VALUE=overridden
39 changes: 39 additions & 0 deletions test/dotenv_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,45 @@ void main() {
expect(dotenv.env['TRIM_SPACE_FROM_UNQUOTED'], 'some spaced out string');
expect(dotenv.env['USERNAME'], '[email protected]');
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'], '[email protected]');
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',
Expand Down