From 55eae6e3c1afaf3bf362cc1d61553bf78bf09dc4 Mon Sep 17 00:00:00 2001 From: James Collins Date: Wed, 12 Dec 2018 12:50:32 +1300 Subject: [PATCH] flutter compatibility 2.0.0 --- .gitignore | 79 ++++++- .travis.yml | 6 - CHANGELOG.md | 17 +- CONTRIBUTING.md | 2 +- LICENSE | 2 +- README.md | 78 +++---- analysis_options.yaml | 179 ++++++++++++++- .../plugins/GeneratedPluginRegistrant.java | 23 ++ android/local.properties | 3 + bin/dotenv.dart | 33 --- bin/new.dart | 61 ------ example/.env.example | 2 - example/README.md | 21 -- example/example.dart | 22 -- flutter-dotenv.iml | 19 ++ ios/Flutter/Generated.xcconfig | 8 + ios/Runner/GeneratedPluginRegistrant.h | 14 ++ ios/Runner/GeneratedPluginRegistrant.m | 12 ++ lib/dotenv.dart | 52 ----- lib/flutter_dotenv.dart | 4 + lib/src/dotenv.dart | 92 ++++++++ lib/src/parser.dart | 43 +--- pubspec.lock | 132 ++++++++++++ pubspec.yaml | 22 +- test/dotenv_test.dart | 54 ----- test/parser_test.dart | 203 ------------------ tool/README.md | 8 - tool/fmt.sh | 2 +- tool/test.sh | 4 - tool/travis.sh | 5 - 30 files changed, 616 insertions(+), 586 deletions(-) delete mode 100644 .travis.yml create mode 100644 android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java create mode 100644 android/local.properties delete mode 100644 bin/dotenv.dart delete mode 100644 bin/new.dart delete mode 100644 example/.env.example delete mode 100644 example/README.md delete mode 100644 example/example.dart create mode 100644 flutter-dotenv.iml create mode 100644 ios/Flutter/Generated.xcconfig create mode 100644 ios/Runner/GeneratedPluginRegistrant.h create mode 100644 ios/Runner/GeneratedPluginRegistrant.m delete mode 100644 lib/dotenv.dart create mode 100644 lib/flutter_dotenv.dart create mode 100644 lib/src/dotenv.dart create mode 100644 pubspec.lock delete mode 100644 test/dotenv_test.dart delete mode 100644 test/parser_test.dart delete mode 100755 tool/test.sh delete mode 100755 tool/travis.sh diff --git a/.gitignore b/.gitignore index 2412749..85f588c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,73 @@ -.buildlog -.pub/ -build/ -packages +###################### +# Project Specific +###################### +*.g.dart doc/ -.env* -!example/.env.example -.dart_tool/ -pubspec.lock + +###################### +# Intellij +###################### +.idea* +*.iml +*.iws +*.ipr +*.ids +*.orig +classes/ +build/ +gen/ + +###################### +# Visual Studio Code +###################### +.vscode/ + +###################### +# Windows +###################### +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +###################### +# Mac OSX +###################### +.DS_Store +.svn + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +###################### +# Flutter & Dart +###################### .packages +.pub/ +.dart_tool/ +.flutter-plugins + +###################### +# Directories +###################### +/bin/ +/deploy/ +/out/ + +###################### +# Logs +###################### +*.log* + +###################### +# Others +###################### +*.class +*.*~ +*~ +.merge_file* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0a148c8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: dart -dart: - - stable - - dev -script: ./tool/travis.sh -sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index b93f7ad..af9c4c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,18 @@ Release notes are available on [github][notes]. [pub-semver]: https://www.dartlang.org/tools/pub/versioning.html#semantic-versions [pub-semver-readme]: https://pub.dartlang.org/packages/pub_semver -[notes]: https://github.com/mockturtl/dotenv/releases +[notes]: https://github.com/java-james/flutter_dotenv/releases + +2.0.0 +----- + +- Flutter compatible 1.0.0 ----- - Dart 2 compatible. [#16][] - #### 0.1.3+3 - [docs] tweak README @@ -65,12 +69,3 @@ Release notes are available on [github][notes]. ----- Initial release. - -[#3]: https://github.com/mockturtl/dotenv/issues/3 -[#5]: https://github.com/mockturtl/dotenv/issues/5 -[#6]: https://github.com/mockturtl/dotenv/issues/6 -[#7]: https://github.com/mockturtl/dotenv/issues/7 -[#8]: https://github.com/mockturtl/dotenv/issues/8 -[#10]: https://github.com/mockturtl/dotenv/issues/10 -[#11]: https://github.com/mockturtl/dotenv/issues/11 -[#16]: https://github.com/mockturtl/dotenv/issues/16 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 420b84d..b578b6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,5 +10,5 @@ contributing - Use [well-formatted commit messages][git-log-fmt]. -[gh-issues]: https://github.com/mockturtl/dotenv/issues +[gh-issues]: https://github.com/java-james/flutter_dotenv/issues [git-log-fmt]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/LICENSE b/LICENSE index 84736e3..e9da25f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 mockturtl +Copyright (c) 2018 java-james Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 645dbb2..db158f2 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,57 @@ -dotenv -====== +flutter_dotenv +============== -Load environment variables at runtime from a `.env` file. - -[![Pub Version][pub-badge]][pub] -[![Build Status][ci-badge]][ci] -[![Documentation][dartdocs-badge]][dartdocs] - -[ci-badge]: https://travis-ci.org/mockturtl/dotenv.svg?branch=master -[ci]: https://travis-ci.org/mockturtl/dotenv -[pub-badge]: https://img.shields.io/pub/v/dotenv.svg -[pub]: https://pub.dartlang.org/packages/dotenv -[dartdocs-badge]: https://img.shields.io/badge/dartdocs-reference-blue.svg -[dartdocs]: http://www.dartdocs.org/documentation/dotenv/latest +Load configuration at runtime from a `.env` file which can be used throughout the applicaiton. ### about -Deploying applications should be simple. This implies constraints: +This library is a fork of [mockturtl/dotenv] dart library with slight changes to make this work with flutter. +It parses the `.env` file into a map contained within a singleton which allows the variables to be used throughout your application. -> **The [twelve-factor app][12fa] stores [config][cfg] in _environment variables_** -> (often shortened to _env vars_ or _env_). Env vars are easy to change -> between deploys without changing any code... they are a language- and -> OS-agnostic standard. - -[12fa]: http://www.12factor.net -[cfg]: http://12factor.net/config - -An _environment_ is the set of variables known to a process (say, `PATH`, `PORT`, ...). -It is desirable to mimic the production environment during development (testing, -staging, ...) by reading these values from a file. - -This library parses that file and merges its values with the built-in -[`Platform.environment`][docs-io] map. - -[docs-io]: https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:io.Platform#id_environment +[mockturtl/dotenv]: https://pub.dartlang.org/packages/dotenv ### usage -See [documentation][usage] and [examples][]. - -[usage]: http://www.dartdocs.org/documentation/dotenv/latest/index.html#dotenv/dotenv -[examples]: https://github.com/mockturtl/dotenv/tree/master/example - -### cli - -Get the latest: +Create a `.env` file in the root of your project with the example content: ```sh -$ pub global activate dotenv +VAR_NAME=HELLOWORLD ``` -Run: +Add the `.env` file to your assets bundle in `pubspec.yaml` -```sh -$ pub global run dotenv:new # create a .env file and add it to .gitignore -$ pub global run dotenv # load the file and print the environment to stdout + ``` + assets: + - .env + ``` + +Init the DotEnv singleton in `main.dart` ``` +Future main() async { + await DotEnv().load('.env'); + //...runapp +} +``` + +Access variables from `.env` throughout the applicaiton +``` +DotEnv().env['VAR_NAME']; +``` + +Optionally you could map `DotEnv().env` after load to a config model to access config with types. #### discussion Use the [issue tracker][tracker] for bug reports and feature requests. -Pull requests gleefully considered. +Pull requests are welcome. -[tracker]: https://github.com/mockturtl/dotenv/issues +[tracker]: https://github.com/java-james/flutter_dotenv/issues ###### prior art +[flutter_dotenv]: https://pub.dartlang.org/packages/dotenv +- [mockturtl/dotenv][] (dart) - [bkeepers/dotenv][] (ruby) - [motdotla/dotenv][] (node) - [theskumar/python-dotenv][] (python) @@ -77,6 +62,7 @@ Pull requests gleefully considered. - [mefellows/sbt-dotenv][] (scala) - [greenspun/dotenv][] (half of common lisp) +[mockturtl/dotenv]: https://pub.dartlang.org/packages/dotenv [bkeepers/dotenv]: https://github.com/bkeepers/dotenv [motdotla/dotenv]: https://github.com/motdotla/dotenv [theskumar/python-dotenv]: https://github.com/theskumar/python-dotenv diff --git a/analysis_options.yaml b/analysis_options.yaml index eae1e42..ca0bc59 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,178 @@ +# ~ Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +# ~ for details. All rights reserved. Use of this source code is governed by a +# ~ BSD-style license that can be found in the LICENSE file. +# ~ +# ~ A living list of rules following the Effective Dart guide. +# ~ https://www.dartlang.org/guides/language/effective-dart +# ~ +# ~ Note: not everything suggested in the guide has a rule associated with it. + analyzer: - strong-mode: - implicit-casts: false \ No newline at end of file + #strong-mode: + #implicit-casts: false + #implicit-dynamic: false + errors: + todo: ignore + #exclude: + +# ~ Effective dart lint rules form https://github.com/dart-lang/linter +# ~ We have implemented all but lines_longer_than_80_chars & public_member_api_docs +linter: + rules: + # ~ --- STYLE + # ~ identifiers + - camel_case_types + - library_names + - file_names + - library_prefixes + - non_constant_identifier_names + - constant_identifier_names # ~ prefer + # ~ ordering + - directives_ordering + # ~ formating + - curly_braces_in_flow_control_structures + # ~ --- DOCUMENTATION + # ~ comments + # ~ doc comments + - slash_for_doc_comments + - package_api_docs # ~ prefer + - comment_references + # ~ markdown + # ~ writing + # ~ --- USAGE + # ~ libraries + - implementation_imports + - avoid_relative_lib_imports + # ~ strings + - prefer_adjacent_string_concatenation + - prefer_interpolation_to_compose_strings # ~ prefer + - unnecessary_brace_in_string_interps # ~ avoid + # ~ collections + - prefer_collection_literals + - avoid_function_literals_in_foreach_calls # ~ avoid + - prefer_iterable_whereType + # ~ functions + - prefer_function_declarations_over_variables + - unnecessary_lambdas + # ~ parameters + - prefer_equal_for_default_values + # ~ variables + - avoid_init_to_null + # ~ members + - unnecessary_getters_setters + - prefer_final_fields + - prefer_expression_function_bodies # consider + - unnecessary_this + - prefer_typing_uninitialized_variables + # ~ constructors + - prefer_initializing_formals + - type_init_formals + - empty_constructor_bodies + - unnecessary_new + - unnecessary_const + # ~ error handling + - avoid_catches_without_on_clauses # ~ avoid + - use_rethrow_when_possible + # ~ asynchrony + # ~ --- DESIGN + # ~ names + - use_to_and_as_if_applicable + # ~ libraries + # ~ classes + - one_member_abstracts # ~ avoid + - avoid_classes_with_only_static_members # ~ avoid + # ~ constructors + - prefer_constructors_over_static_methods + # ~ members + - use_setters_to_change_properties + - avoid_setters_without_getters + - avoid_returning_null # ~ avoid + - avoid_returning_this # ~ avoid + # ~ types + - type_annotate_public_apis # ~ prefer + - omit_local_variable_types # ~ avoid + - avoid_types_on_closure_parameters # ~ avoid + - avoid_return_types_on_setters + - prefer_generic_function_type_aliases + - avoid_private_typedef_functions # ~ prefer + # ~ parameters + - avoid_positional_boolean_parameters # ~ avoid + # ~ equality + - hash_and_equals + - avoid_null_checks_in_equality_operators + +# ~ Rules that are not used but are recommended by effective dart: +# +# - public_member_api_docs # ~ We don't have time to document everything yet +# - lines_longer_than_80_chars # prefer # ~ Doesn't seem reasonable + +# ~ Rules that are not used +# - always_declare_return_types +# - always_put_control_body_on_new_line +# - always_put_required_named_parameters_first +# - always_require_non_null_named_parameters +# - always_specify_types +# - annotate_overrides +# - avoid_annotating_with_dynamic +# - avoid_as +# - avoid_bool_literals_in_conditional_expressions +# - avoid_catching_errors +# - avoid_double_and_int_checks +# - avoid_empty_else +# - avoid_field_initializers_in_const_classes +# - avoid_js_rounded_ints +# - avoid_renaming_method_parameters +# - avoid_single_cascade_in_expression_statements +# - avoid_slow_async_io +# - avoid_types_as_parameter_names +# - avoid_unused_constructor_parameters +# - await_only_futures +# - cancel_subscriptions +# - cascade_invocations +# - close_sinks +# - control_flow_in_finally +# - empty_catches +# - empty_statements +# - invariant_booleans +# - iterable_contains_unrelated_type +# - join_return_with_assignment +# - list_remove_unrelated_type +# - literal_only_boolean_expressions +# - no_adjacent_strings_in_list +# - no_duplicate_case_values +# - null_closures +# - only_throw_errors +# - overridden_fields +# - package_names +# - package_prefixed_library_names +# - parameter_assignments +# - prefer_asserts_in_initializer_lists +# - prefer_bool_in_asserts +# - prefer_conditional_assignment +# - prefer_const_constructors +# - prefer_const_constructors_in_immutables +# - prefer_const_declarations +# - prefer_const_literals_to_create_immutables +# - prefer_contains +# - prefer_expression_function_bodies +# - prefer_final_locals +# - prefer_foreach +# - prefer_is_empty +# - prefer_is_not_empty +# - prefer_single_quotes +# - recursive_getters +# - sort_constructors_first +# - sort_unnamed_constructors_first +# - super_goes_last +# - test_types_in_equals +# - throw_in_finally +# - unawaited_futures +# - unnecessary_null_aware_assignments +# - unnecessary_null_in_if_null_operators +# - unnecessary_overrides +# - unnecessary_parenthesis +# - unnecessary_statements +# - unrelated_type_equality_checks +# - use_string_buffers +# - valid_regexps +# - void_checks \ No newline at end of file diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..d007606 --- /dev/null +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,23 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/android/local.properties b/android/local.properties new file mode 100644 index 0000000..eb2821d --- /dev/null +++ b/android/local.properties @@ -0,0 +1,3 @@ +sdk.dir=/Users/jamescollins/Library/Android/sdk +flutter.sdk=/Users/jamescollins/Development/sdks/flutter +flutter.versionName=2.0.0 \ No newline at end of file diff --git a/bin/dotenv.dart b/bin/dotenv.dart deleted file mode 100644 index c3abac1..0000000 --- a/bin/dotenv.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; - -import 'package:args/args.dart'; -import 'package:dotenv/dotenv.dart' as dotenv; - -final _argPsr = new ArgParser() - ..addFlag('help', abbr: 'h', negatable: false, help: 'Print this help text.') - ..addOption('file', - abbr: 'f', - defaultsTo: '.env', - help: - 'File to read.\nProvides environment variable definitions, one per line.'); - -/// Prints the [env] map. -/// -/// ## usage -/// -/// pub global run dotenv --help -void main(List argv) { - var opts = _argPsr.parse(argv); - - if (opts['help'] == true) return _usage(); - - dotenv.load(opts['file'] as String); - _p(dotenv.env); -} - -void _usage() { - _p('Parse variable definitions from a file, print the environment and exit.'); - _p('Usage: pub global run dotenv [-f ]\n${_argPsr.usage}'); -} - -void _p(msg) => stdout.writeln(msg); diff --git a/bin/new.dart b/bin/new.dart deleted file mode 100644 index 2f9c226..0000000 --- a/bin/new.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:io' hide FileMode; -import 'package:args/args.dart'; -import 'package:dart2_constant/io.dart'; - -const String gitignore = '.gitignore'; - -final _argPsr = new ArgParser() - ..addFlag('help', abbr: 'h', negatable: false, help: 'Print this help text.') - ..addOption('file', - abbr: 'f', - defaultsTo: '.env', - help: 'File to create.\nDo not check secrets into version control!'); - -/// Creates `.env` if the file does not exist, and adds it to `.gitignore`. -/// -/// ## usage -/// -/// pub global run dotenv:new --help -void main(List args) { - var opts = _argPsr.parse(args); - - if (opts['help'] == true) return _usage(); - - String envFile = opts['file'].toString(); - _touch(envFile); - - var g = _touch(gitignore); - if (_anyLineContains(envFile, g)) return _handleIgnored(envFile); - - _appendTo(g, '$envFile*'); -} - -bool _anyLineContains(String str, File f) => - f.readAsLinesSync().any((line) => line.contains(str)); - -void _handleIgnored(String filename) { - _pErr("Found $gitignore with line containing '$filename'; exiting."); - exitCode = 1; -} - -File _touch(String filename) { - var f = new File.fromUri(new Uri.file(filename)); - if (f.existsSync()) return f; - - f.createSync(); - _p('Created file: $filename'); - return f; -} - -void _appendTo(File f, String line) { - f.writeAsStringSync('$line\n', mode: FileMode.append); - _p("Added '$line' to ${f.path}."); -} - -void _usage() { - _p('Create a new file and gitignore it.'); - _p('Usage: pub global run dotenv:new [-f ]\n${_argPsr.usage}'); -} - -void _p(String msg) => stdout.writeln(msg); -void _pErr(String msg) => stderr.writeln(msg); diff --git a/example/.env.example b/example/.env.example deleted file mode 100644 index 50e8181..0000000 --- a/example/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -foo = 'bar' # comments and whitespace will be stripped -export baz="qux" diff --git a/example/README.md b/example/README.md deleted file mode 100644 index d608bff..0000000 --- a/example/README.md +++ /dev/null @@ -1,21 +0,0 @@ -example -======= - -Note consuming code must call `load` before accessing the `env` map. - -usage ------ - -From this directory, run - -```sh -$ dart example.dart -``` - -###### setup - -Define variables in a `.env` file. - -```sh -$ cp .env.example .env -``` diff --git a/example/example.dart b/example/example.dart deleted file mode 100644 index 782f6ef..0000000 --- a/example/example.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:io'; - -import 'package:dotenv/dotenv.dart' show load, clean, isEveryDefined, env; - -void main() { - load(); - - p('read all vars? ${isEveryDefined(['foo', 'baz'])}'); - - p('value of foo is ${env['foo']}'); - p('value of baz is ${env['baz']}'); - p('your home directory is: ${env['HOME']}'); - - clean(); - - p('cleaned!'); - p('env has key foo? ${env.containsKey('foo')}'); - p('env has key baz? ${env.containsKey('baz')}'); - p('your home directory is still: ${env['HOME']}'); -} - -p(String msg) => stdout.writeln(msg); diff --git a/flutter-dotenv.iml b/flutter-dotenv.iml new file mode 100644 index 0000000..0e5878c --- /dev/null +++ b/flutter-dotenv.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/Flutter/Generated.xcconfig b/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000..7fb417e --- /dev/null +++ b/ios/Flutter/Generated.xcconfig @@ -0,0 +1,8 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/jamescollins/Development/sdks/flutter +FLUTTER_APPLICATION_PATH=/Users/jamescollins/Development/spark/flutter-dotenv +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/Users/jamescollins/Development/sdks/flutter/bin/cache/artifacts/engine/ios +FLUTTER_BUILD_NAME=0.1.0 diff --git a/ios/Runner/GeneratedPluginRegistrant.h b/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 0000000..3b700eb --- /dev/null +++ b/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +#endif /* GeneratedPluginRegistrant_h */ diff --git a/ios/Runner/GeneratedPluginRegistrant.m b/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000..60dfa42 --- /dev/null +++ b/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { +} + +@end diff --git a/lib/dotenv.dart b/lib/dotenv.dart deleted file mode 100644 index a9a2489..0000000 --- a/lib/dotenv.dart +++ /dev/null @@ -1,52 +0,0 @@ -/// 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 'src/parser.dart'; - -var _env = new Map.from(Platform.environment); - -/// A copy of [Platform.environment](dart:io) including variables loaded at runtime from a file. -Map get env => _env; - -/// Overwrite [env] with a new writable copy of [Platform.environment](dart:io). -Map clean() => _env = new 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 vars) => - vars.every((k) => _env[k] != null && _env[k].isNotEmpty); - -/// 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 f = new File.fromUri(new Uri.file(filename)); - var lines = _verify(f); - _env.addAll(psr.parse(lines)); -} - -List _verify(File f) { - if (f.existsSync()) return f.readAsLinesSync(); - stderr.writeln('[dotenv] Load failed: file not found: $f'); - return []; -} diff --git a/lib/flutter_dotenv.dart b/lib/flutter_dotenv.dart new file mode 100644 index 0000000..cb3f9b0 --- /dev/null +++ b/lib/flutter_dotenv.dart @@ -0,0 +1,4 @@ +library flutterdotenv; + +export 'src/dotenv.dart'; +export 'src/parser.dart'; diff --git a/lib/src/dotenv.dart b/lib/src/dotenv.dart new file mode 100644 index 0000000..f112af8 --- /dev/null +++ b/lib/src/dotenv.dart @@ -0,0 +1,92 @@ +/// Loads environment variables from a `.env` file. +/// +/// ## usage +/// +/// Once you call [load] or the factory constructor with a valid env, the top-level [env] map is available. +/// You may wish to prefix the import. +/// +/// import 'package:flutter_dotenv/flutter_dotenv.dart' show load, env; +/// +/// void main() { +/// await DotEnv().load('.env'); +/// runApp(App()); +/// var x = DotEnv().env['foo']; +/// // ... +/// } +/// +/// Verify required variables are present: +/// +/// const _requiredEnvVars = const ['host', 'port']; +/// bool get hasEnv => isEveryDefined(_requiredEnvVars); +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; + +import './parser.dart'; + +/// +/// ## usage +/// +/// Future main() async { +/// await DotEnv().load('.env'); +/// //...runapp +/// } +/// +/// Verify required variables are present: +/// +/// const _requiredEnvVars = const ['host', 'port']; +/// bool get hasEnv => isEveryDefined(_requiredEnvVars); + +class DotEnv { + Map _env = {}; + static DotEnv _singleton; + + Map get env { + if (_env.isEmpty) { + stderr.writeln( + '[flutter_dotenv] No env values found. Make sure you have called DotEnv.load()'); + } + return _env; + } + + set env(Map env) { + _env = env; + } + + /// 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) => env[k] != null && env[k].isNotEmpty); + + /// Read environment variables from [filename] and add them to [env]. + /// Logs to [stderr] if [filename] does not exist. + Future load([String filename = '.env', Parser psr = const Parser()]) async { + var lines = await _verify(filename); + env.addAll(psr.parse(lines)); + } + + Future> _verify(String filename) async { + try { + var str = await rootBundle.loadString(filename); + if (str.isNotEmpty) return str.split('\n'); + stderr.writeln('[flutter_dotenv] Load failed: file $filename was empty'); + } on FlutterError { + stderr.writeln('[flutter_dotenv] Load failed: file not found'); + } + return []; + } + + factory DotEnv({Map env}) { + if (_singleton == null) { + _singleton = DotEnv._internal(env: env); + } + return _singleton; + } + + DotEnv._internal({Map env}) + : _env = env ?? {}; +} diff --git a/lib/src/parser.dart b/lib/src/parser.dart index bc203fb..e96f0bb 100644 --- a/lib/src/parser.dart +++ b/lib/src/parser.dart @@ -1,35 +1,32 @@ -part of dotenv; +import 'package:meta/meta.dart'; /// Creates key-value pairs from strings formatted as environment /// variable definitions. class Parser { - static const _singleQuot = "'"; static const _keyword = 'export'; - static final _comment = new RegExp(r'''#.*(?:[^'"])$'''); - static final _surroundQuotes = new RegExp(r'''^(['"])(.*)\1$'''); - static final _bashVar = - new RegExp(r'(?:\\)?(\$)(?:{)?([a-zA-Z_][\w]*)+(?:})?'); + static final _comment = RegExp(r'''#.*(?:[^'"])$'''); + static final _surroundQuotes = RegExp(r'''^(['"])(.*)\1$'''); /// [Parser] methods are pure functions. const Parser(); - /// Creates a [Map](dart:core) suitable for merging into [Platform.environment](dart:io). + /// Creates a [Map](dart:core) /// Duplicate keys are silently discarded. Map parse(Iterable lines) { var out = {}; - lines.forEach((line) { + for (var line in lines) { var kv = parseOne(line, env: out); - if (kv.isEmpty) return; + if (kv.isEmpty) continue; out.putIfAbsent(kv.keys.single, () => kv.values.single); - }); + } return out; } /// Parses a single line into a key-value pair. @visibleForTesting Map parseOne(String line, - {Map env: const {}}) { + {Map env = const {}}) { var stripped = strip(line); if (!_isValid(stripped)) return {}; @@ -39,24 +36,11 @@ class Parser { if (k.isEmpty) return {}; var rhs = sides[1].trim(); - var quotChar = surroundingQuote(rhs); var v = unquote(rhs); - if (quotChar == _singleQuot) // skip substitution in single-quoted values - return {k: v}; - - return {k: interpolate(v, env)}; + return {k: v}; } - /// Substitutes $bash_vars in [val] with values from [env]. - @visibleForTesting - String interpolate(String val, Map env) => - val.replaceAllMapped(_bashVar, (m) { - var k = m.group(2); - if (!_has(env, k)) return _tryPlatformEnv(k); - return env[k]; - }); - /// If [val] is wrapped in single or double quotes, returns the quote character. /// Otherwise, returns the empty string. @visibleForTesting @@ -79,13 +63,4 @@ class Parser { 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 map, String key) => - map.containsKey(key) && map[key] != null; - - String _tryPlatformEnv(String key) { - if (!_has(Platform.environment, key)) return ''; - return Platform.environment[key]; - } } diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..70370f4 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,132 @@ +# Generated by pub +# See https://www.dartlang.org/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.3+1" + meta: + dependency: "direct main" + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.8" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.0.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 30acefa..58aa9e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,14 @@ -name: dotenv -version: 1.0.0 -description: Load environment variables from a `.env` file. -author: mockturtl -homepage: https://github.com/mockturtl/dotenv +name: flutter_dotenv +version: 2.0.0 +description: Load global app config from a `.env` file. +author: java-james +homepage: https://github.com/java-james/flutter_dotenv environment: - sdk: '>=1.8.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dependencies: - args: ^1.0.0 - dart2_constant: ^1.0.0 - meta: ^1.0.2 + flutter: + sdk: flutter + meta: 1.1.6 dev_dependencies: - test: any - collection: any + flutter_test: + sdk: flutter diff --git a/test/dotenv_test.dart b/test/dotenv_test.dart deleted file mode 100644 index 5f513db..0000000 --- a/test/dotenv_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'dart:io'; - -import 'package:collection/collection.dart' show MapEquality; -import 'package:dotenv/dotenv.dart' as dotenv; -import 'package:test/test.dart'; - -void main() { - group('[dotenv]', () { - var subj = new DotenvTest(); - - setUp(() => dotenv.env.addAll(vars)); - tearDown(() => dotenv.clean()); - - test('it can clean previously defined variables', subj.clean); - test('it is equal to the read-only process environment when clean', - subj.clean2); - test('it confirms all required vars are defined', subj.every); - test('it fails when a required var is not defined', subj.every_fail); - test('it loads the file', subj.load, skip: 'pending'); - }); -} - -const extra = const {'servlets': 'yes', 'rats': 'yes', 'horses': 'omgyes'}; -const vars = const {'x': '1', 'y': 'false', 'z': 'foo', 'empty': ''}; - -class DotenvTest { - void clean() { - dotenv.env.addAll(extra); - dotenv.clean(); - extra.keys.forEach((k) => expect(dotenv.env.containsKey(k), isFalse)); - vars.keys.forEach((k) => expect(dotenv.env.containsKey(k), isFalse)); - } - - void clean2() { - expect(_clean, isFalse); - dotenv.clean(); - expect(_clean, isTrue); - } - - void every() { - dotenv.env.addAll(extra); - expect(dotenv.isEveryDefined(['x', 'y', 'z']), isTrue); - expect(dotenv.isEveryDefined(['servlets', 'rats', 'horses']), isTrue); - } - - void every_fail() { - expect(dotenv.isEveryDefined(['empty']), isFalse); - expect(dotenv.isEveryDefined(['no_such_key']), isFalse); - } - - void load() {} -} - -bool get _clean => const MapEquality().equals(dotenv.env, Platform.environment); diff --git a/test/parser_test.dart b/test/parser_test.dart deleted file mode 100644 index d1aeb79..0000000 --- a/test/parser_test.dart +++ /dev/null @@ -1,203 +0,0 @@ -import 'dart:io'; -import 'dart:math'; - -import 'package:dotenv/dotenv.dart'; -import 'package:test/test.dart'; - -const ceil = 100000; -Random rand; - -void main() { - group('[Parser]', () { - setUp(() => rand = new Random()); - var subj = new ParserTest(); - test('it swallows "export"', subj.swallow); - - test('it strips trailing comments', subj.strip); - test('it ignores comment lines', subj.strip_line); - - test('it handles unquoted values', subj.unquote_noop); - test('it handles double quoted values', subj.unquote_double); - test('it handles single quoted values', subj.unquote_single); - test('it handles escaped quotes within values', subj.unquote_escape); - - test('it skips empty lines', subj.parse_empty); - test('it ignores duplicate keys', subj.parse_dup); - test('it substitutes known variables into other values', subj.parse_subs); - test('it discards surrounding quotes', subj.parse_quot); - - test('it detects unquoted values', subj.surroundingQuote_none); - test('it detects double-quoted values', subj.surroundingQuote_double); - test('it detects single-quoted values', subj.surroundingQuote_single); - - test('it performs variable substitution', subj.interpolate); - test('it skips undefined variables', subj.interpolate_missing); - test('it handles explicitly null values in env', subj.interpolate_missing2); - test('it falls back to the process environment for undefined variables', - subj.interpolate_fallback); - test('it handles \${surrounding braces} on vars', subj.interpolate_curlies); - - test('it knows quoted # is not a comment', subj.parseOne_pound); - test('it handles quotes in a comment', - subj.parseOne_commentQuote_terminalChar); - test('it does NOT handle comments ending with a quote', - subj.parseOne_commentQuote_terminalChar2); - test('it skips var substitution in single quotes', subj.parseOne_quot); - test('it performs var subs in double quotes', subj.parseOne_doubleQuot); - test('it performs var subs without quotes', subj.parseOne_unQuot); - }); -} - -const _psr = const Parser(); - -class ParserTest { - void parseOne_commentQuote_terminalChar2() { - var fail = - _psr.parseOne('fruit = banana # I\'m a comment with a final "quote"'); - expect( - fail['fruit'], equals('banana # I\'m a comment with a final "quote"')); - } - - void parseOne_commentQuote_terminalChar() { - // note terminal whitespace - var sing = _psr.parseOne("fruit = 'banana' # comments can be 'sneaky!' "); - var doub = _psr.parseOne('fruit = "banana" # comments can be "sneaky!" '); - var none = _psr.parseOne('fruit = banana # comments can be "sneaky!" '); - - expect(sing['fruit'], equals('banana')); - expect(doub['fruit'], equals('banana')); - expect(none['fruit'], equals('banana')); - } - - void parseOne_pound() { - var double = _psr.parseOne('foo = "ab#c"'); - var single = _psr.parseOne("foo = 'ab#c'"); - - expect(double['foo'], equals('ab#c')); - expect(single['foo'], equals('ab#c')); - } - - void interpolate() { - var out = _psr.interpolate(r'a$foo$baz', {'foo': 'bar', 'baz': 'qux'}); - expect(out, equals('abarqux')); - } - - void interpolate_missing() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.interpolate('a\$jinx_$r', {}); - expect(out, equals('a')); - } - - void interpolate_missing2() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.interpolate('a\$foo_$r\$baz_$r', {'foo_$r': null}); - expect(out, equals('a')); - } - - void interpolate_fallback() { - var out = _psr.interpolate('a\$HOME', {}); - expect(out, equals('a${Platform.environment['HOME']}')); - } - - void interpolate_curlies() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.interpolate('optional_\${foo_$r}', {'foo_$r': 'curlies'}); - expect(out, equals('optional_curlies')); - } - - void parseOne_quot() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.parseOne("some_var='my\$key_$r'", env: {'key_$r': 'val'}); - expect(out['some_var'], equals('my\$key_$r')); - } - - void parseOne_doubleQuot() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.parseOne('some_var="my\$key_$r"', env: {'key_$r': 'val'}); - expect(out['some_var'], equals('myval')); - } - - void parseOne_unQuot() { - var r = rand.nextInt(ceil); // avoid runtime collision with real env vars - var out = _psr.parseOne("some_var=my\$key_$r", env: {'key_$r': 'val'}); - expect(out['some_var'], equals('myval')); - } - - void surroundingQuote_none() { - var out = _psr.surroundingQuote('no quotes here!'); - expect(out, isEmpty); - } - - void surroundingQuote_single() { - var out = _psr.surroundingQuote("'single quoted'"); - expect(out, equals("'")); - } - - void surroundingQuote_double() { - var out = _psr.surroundingQuote('"double quoted"'); - expect(out, equals('"')); - } - - void swallow() { - var out = _psr.swallow(' export foo = bar '); - expect(out, equals('foo = bar')); - } - - void strip() { - var out = _psr.strip( - 'needs=explanation # It was the year when they finally immanentized the Eschaton.'); - expect(out, equals('needs=explanation')); - } - - void strip_line() { - var out = - _psr.strip(' # It was the best of times, it was a waste of time.'); - expect(out, isEmpty); - } - - void unquote_single() { - var out = _psr.unquote("'val'"); - expect(out, equals('val')); - } - - void unquote_noop() { - var out = _psr.unquote('str'); - expect(out, equals('str')); - } - - void unquote_double() { - var out = _psr.unquote('"val"'); - expect(out, equals('val')); - } - - void unquote_escape() { - var out = _psr.unquote("val_with_\"escaped\"_\'quote\'s"); - expect(out, equals('''val_with_"escaped"_'quote's''')); - } - - void parse_empty() { - var out = _psr.parse([ - '# Define environment variables.', - ' # comments will be stripped', - 'foo=bar # trailing junk', - ' baz = qux', - '# another comment' - ]); - expect(out, equals({'foo': 'bar', 'baz': 'qux'})); - } - - void parse_dup() { - var out = _psr.parse(['foo=bar', 'foo=baz']); - expect(out, equals({'foo': 'bar'})); - } - - void parse_subs() { - var out = _psr.parse(['foo=bar', r'baz=super$foo']); - expect(out, equals({'foo': 'bar', 'baz': 'superbar'})); - } - - void parse_quot() { - var out = _psr.parse([r"foo = 'bar'", r'export baz="qux"']); - expect(out, equals({'foo': 'bar', 'baz': 'qux'})); - } -} diff --git a/tool/README.md b/tool/README.md index b79ca1f..a9b17dd 100644 --- a/tool/README.md +++ b/tool/README.md @@ -17,10 +17,6 @@ Runs the official [code formatter][]. $ pub global activate dart_style ``` -### [test.sh](test.sh) - -Runs the unit test suite. - ### [docs.sh](docs.sh) Preview [dartdoc][] documentation. @@ -33,10 +29,6 @@ Preview [dartdoc][] documentation. $ pub global activate dartdoc ``` -### [travis.sh](travis.sh) - -Run the analyzer and unit tests on Travis CI. - ### [release.sh](release.sh) `git tag` and `pub publish`. diff --git a/tool/fmt.sh b/tool/fmt.sh index c541237..bf4fbf7 100755 --- a/tool/fmt.sh +++ b/tool/fmt.sh @@ -2,4 +2,4 @@ # Autoformat code in-place, per style guidelines. -dartfmt -w bin lib test example +dartfmt -w ../lib diff --git a/tool/test.sh b/tool/test.sh deleted file mode 100755 index f8d9f0c..0000000 --- a/tool/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -j=$(nproc) - -pub run test -j$j diff --git a/tool/travis.sh b/tool/travis.sh deleted file mode 100755 index 19213a9..0000000 --- a/tool/travis.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -e - -dartanalyzer --fatal-warnings {bin,lib,example}/*.dart - -pub run test