diff --git a/.gitignore b/.gitignore index 3a85790..555e940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +.idea/ +.vscode/ + # https://dart.dev/guides/libraries/private-files # Created by `dart pub` .dart_tool/ +pubspec.lock +lib/Env* diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef040f..17583ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.0 + +- Adds encoder support. +- Produces raw arguments in the generated file to help regenerate. + ## 0.3.7 - Prevent generating from a project without envs. diff --git a/README.md b/README.md index ba6848a..f070f76 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ env2dart -a, --active Specify the environment variables to use. For example, if -active prod is specified, the CLI will look for the .env.prod file and merge it with the .env file. -c, --class Specify the name for the generated class (defaults to "Env") +-e, --encoder Encode value using the encoder to avoid raw strings. Allows 'base64' and 'utf8'. -h, --help View help options. ``` diff --git a/analysis_options.yaml b/analysis_options.yaml index 0eb6447..f0e1273 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,6 @@ include: all_lint_rules.yaml analyzer: errors: - import_of_legacy_library_into_null_safe: ignore # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. # We explicitly enabled even conflicting rules and are fixing the conflict # in this file diff --git a/lib/env2dart.dart b/lib/env2dart.dart index bfd13b3..727253f 100644 --- a/lib/env2dart.dart +++ b/lib/env2dart.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; @@ -25,12 +26,16 @@ EnvParser _newParser(String contents) { } const kIgnoreLints = [ + 'avoid_escaping_inner_quotes', 'camel_case_types', 'non_constant_identifier_names', 'prefer_single_quotes', - 'avoid_escaping_inner_quotes', + 'require_trailing_commas', ]; +const kEncoderBase64 = 'base64'; +const kEncoderUtf8 = 'utf8'; + Map _resolvePairs(File file, String fileName) { final input = file.readAsStringSync(); final parser = _newParser(input); @@ -77,10 +82,12 @@ void _mergeEnv( } void envgen({ - String? output, + required List rawArguments, required String path, - String? active, + required String? output, required String clazz, + required String? active, + required String? encoder, }) { 'Generating, please wait.'.$info(tag: 'env2dart'); final sw = Stopwatch()..start(); @@ -97,7 +104,7 @@ void envgen({ final fileName = e.uri.pathSegments.last; final pairs = _resolvePairs(e, fileName); final columns = [ - ['KEY', 'VALUE'] + ['KEY', 'VALUE'], ]; for (final pair in pairs.values) { columns.add([pair.name, pair.value.toString()]); @@ -156,7 +163,7 @@ void envgen({ nullableKeys: nullableKeys, useEnvValue: true, ); - impls.add(_toSubenv(clazz, entry)); + impls.add(_toSubenv(clazz, entry, encoder: encoder)); final key = entry.key.substring('.env.'.length); final className = '$clazz $key'.pascalCase; extFields.add( @@ -195,13 +202,14 @@ void envgen({ othersKey: otherKeys, extFields: extFields, name: clazz, + encoder: encoder, ); body = [absClass, ...impls]; } else { - body = [_toAbs(d, othersKey: {}, name: clazz)]; + body = [_toAbs(d, othersKey: {}, name: clazz, encoder: encoder)]; } } else { - body = _toEnvs(envs, name: clazz); + body = _toEnvs(envs, name: clazz, encoder: encoder); } final library = Library( (b) => b @@ -212,9 +220,13 @@ void envgen({ '======================================', 'GENERATED CODE - DO NOT MODIFY BY HAND', '======================================', + 'Generate command: env2dart ${rawArguments.join(' ')}', + ]) + ..directives = ListBuilder([ + if (encoder != null) Directive.import('dart:convert'), ]), ); - final dartEmitter = DartEmitter(); + final dartEmitter = DartEmitter(orderDirectives: true); var code = library.accept(dartEmitter).toString(); code = DartFormatter(fixes: StyleFix.all).format(code); output ??= './lib/${clazz.snakeCase}.dart'; @@ -251,6 +263,7 @@ Class _toAbs( Set othersKey = const {}, List extFields = const [], required String name, + String? encoder, }) { final getters = []; final fields = []; @@ -293,11 +306,7 @@ Class _toAbs( b.type = Reference('$fieldType?'); } else { b.type = Reference(fieldType); - String v = field.value.toString(); - if (fieldType == 'String' && - !RegExp('^[\'"].*[\'"]\$').hasMatch(v)) { - v = "'$v'"; - } + final v = field.valueWith(encoder: encoder); b.assignment = Code(v); } }, @@ -378,7 +387,7 @@ Class _toAbs( (b) => b ..name = 'json' ..type = const Reference('Map'), - ) + ), ]), ), Method( @@ -412,7 +421,7 @@ Class _toAbs( ..body = Code( 'final sb = StringBuffer();\n$toString\nreturn sb.toString();', ), - ) + ), ]) ..constructors = ListBuilder([ Constructor( @@ -422,7 +431,7 @@ Class _toAbs( (b) => b ..name = 'env' ..toThis = true, - ) + ), ]), ), ]), @@ -432,14 +441,18 @@ Class _toAbs( List _toEnvs( Map> envs, { required String name, + String? encoder, }) { - return envs.entries.map((e) => _toEnvClass(name, e)).toList(growable: false); + return envs.entries + .map((e) => _toEnvClass(name, e, encoder: encoder)) + .toList(growable: false); } Class _toSubenv( String name, - MapEntry> env, -) { + MapEntry> env, { + String? encoder, +}) { final ovcodes = StringBuffer(); for (final field in env.value.values) { ovcodes.writeln( @@ -448,10 +461,7 @@ Class _toSubenv( .map((e) => ' // $e') .join('\n'), ); - String v = field.value.toString(); - if (field.type == 'String' && !RegExp('^[\'"].*[\'"]\$').hasMatch(v)) { - v = "'$v'"; - } + final v = field.valueWith(encoder: encoder); ovcodes.writeln('_${field.name} = $v;'); } final ek = env.key.substring('.env.'.length); @@ -474,8 +484,9 @@ Class _toSubenv( Class _toEnvClass( String name, - MapEntry> env, -) { + MapEntry> env, { + String? encoder, +}) { final getters = []; final fields = []; final ovps = []; @@ -502,10 +513,12 @@ Class _toEnvClass( ); fields.add( Field( - (b) => b - ..type = Reference(fieldType) - ..name = '_$fieldName' - ..assignment = Code(field.value.toString()), + (b) { + b + ..type = Reference(fieldType) + ..name = '_$fieldName' + ..assignment = Code(field.valueWith(encoder: encoder)); + }, ), ); ovps.add( @@ -576,7 +589,7 @@ Class _toEnvClass( (b) => b ..name = 'json' ..type = const Reference('Map'), - ) + ), ]), ), Method( @@ -625,12 +638,18 @@ Class _toEnvClass( void parseAndGen(List arguments) { final args = ArgParser(); + args.addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'View help options.', + ); args.addOption( 'path', abbr: 'p', defaultsTo: '', - help: - 'Specify working directory, the CLI will look for the .env file in the current directory.', + help: 'Specify working directory, ' + 'the CLI will look for the .env file in the current directory.', ); args.addOption( 'output', @@ -638,23 +657,26 @@ void parseAndGen(List arguments) { help: 'Specify the output file path.', defaultsTo: 'lib/env.g.dart', ); - args.addOption( - 'active', - abbr: 'a', - help: - 'Specify the environment variables to use. For example, if -active prod is specified, the CLI will look for the .env.prod file and merge it with the .env file.', - ); args.addOption( 'class', abbr: 'c', defaultsTo: 'Env', help: 'Specify the name for the generated class', ); - args.addFlag( - 'help', - abbr: 'h', - negatable: false, - help: 'View help options.', + args.addOption( + 'active', + abbr: 'a', + help: 'Specify the environment variables to use. ' + 'For example, if -active prod is specified, ' + 'the CLI will look for the .env.prod file ' + 'and merge it with the .env file.', + ); + args.addOption( + 'encoder', + abbr: 'e', + allowed: [kEncoderBase64, kEncoderUtf8], + help: 'Encode value using the encoder to avoid raw strings. ' + "Allows 'base64' and 'utf8'.", ); final parse = args.parse(arguments); if (parse['help'] == true) { @@ -662,9 +684,40 @@ void parseAndGen(List arguments) { return; } envgen( + rawArguments: arguments, path: parse['path'], output: parse['output'], - active: parse['active'], clazz: parse['class'], + active: parse['active'], + encoder: parse['encoder'], ); } + +final _stringRegExp = RegExp('^[\'"](.*)[\'"]\$'); + +extension on String { + String formalizedWith({String? encoder}) { + String v = replaceAllMapped( + _stringRegExp, + (match) => match.group(1) ?? match.group(0)!, + ); + if (encoder == kEncoderBase64) { + v = 'utf8.decode(' + "base64.decode('${base64.encode(v.codeUnits)}',)," + ')'; + } else if (encoder == kEncoderUtf8) { + v = 'utf8.decode(${utf8.encode(v)})'; + } + return v; + } +} + +extension on KeyValue { + String valueWith({String? encoder}) { + String v = value.toString().formalizedWith(encoder: encoder); + if (encoder == null && type == 'String' && !_stringRegExp.hasMatch(v)) { + v = "'$v'"; + } + return v; + } +} diff --git a/lib/log.dart b/lib/log.dart index 1f893c0..60145f5 100644 --- a/lib/log.dart +++ b/lib/log.dart @@ -114,6 +114,7 @@ extension Log on Object? { Object? error, StackTrace? stackTrace, }) { + // ignore: deprecated_member_use, (whereNotNull) final es = [error, stackTrace].whereNotNull().join('\n\n'); final hasError = es.isNotEmpty; final buffer = StringBuffer(); diff --git a/pubspec.yaml b/pubspec.yaml index 1524819..c742d7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: env2dart description: A simple way to generate `dart` code from a `.env` file. -version: 0.3.7 +version: 0.4.0 homepage: https://github.com/fluttercandies repository: https://github.com/fluttercandies/env2dart issue_tracker: https://github.com/fluttercandies/env2dart/issues