Skip to content

Commit

Permalink
feat: add support for raw strings and no interpolation (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
techouse authored Mar 11, 2024
1 parent c2bf1fe commit ca04a3e
Show file tree
Hide file tree
Showing 21 changed files with 349 additions and 143 deletions.
3 changes: 2 additions & 1 deletion examples/envied_example/.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ key4=123
key5=false
key6=http://foo.bar/baz
key7=2023-11-06T23:09:51.123Z
key8=lorem
key8=lorem
key9=uneascaped$
3 changes: 2 additions & 1 deletion examples/envied_example/.env_debug
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ key4=456
key5=true
key6=http://zomg.test/bbq
key7=2022-09-01T12:01:12.001Z
key8=ipsum
key8=ipsum
key9=unescaped$
1 change: 1 addition & 0 deletions examples/envied_example/lib/app_env_fields.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ abstract interface class AppEnvFields {
abstract final Uri key6;
abstract final DateTime key7;
abstract final ExampleEnum key8;
abstract final String key9;
}
1 change: 1 addition & 0 deletions examples/envied_example/lib/constant_case_env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ part 'constant_case_env.g.dart';
final class ConstantCaseEnv {
@EnviedField()
static const String key1 = _ConstantCaseEnv.key1;

@EnviedField()
static const String key2 = _ConstantCaseEnv.key2;
}
10 changes: 10 additions & 0 deletions examples/envied_example/lib/debug_env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,36 @@ final class DebugEnv implements AppEnv, AppEnvFields {
@override
@EnviedField(varName: 'KEY1')
final String key1 = _Env.key1;

@override
@EnviedField(varName: 'KEY2')
final String key2 = _Env.key2;

@override
@EnviedField()
final String key3 = _Env.key3;

@override
@EnviedField()
final int key4 = _Env.key4;

@override
@EnviedField()
final bool key5 = _Env.key5;

@override
@EnviedField()
final Uri key6 = _Env.key6;

@override
@EnviedField()
final DateTime key7 = _Env.key7;

@override
@EnviedField()
final ExampleEnum key8 = _Env.key8;

@override
@EnviedField(rawString: true)
final String key9 = _Env.key9;
}
2 changes: 2 additions & 0 deletions examples/envied_example/lib/debug_env.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions examples/envied_example/lib/env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ final class Env {

@EnviedField()
static final ExampleEnum key8 = _Env.key8;

@EnviedField(rawString: true)
static final String key9 = _Env.key9;
}
2 changes: 2 additions & 0 deletions examples/envied_example/lib/env.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions examples/envied_example/lib/release_env.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,36 @@ final class ReleaseEnv implements AppEnv, AppEnvFields {
@override
@EnviedField(varName: 'KEY1')
final String key1 = _Env.key1;

@override
@EnviedField(varName: 'KEY2')
final String key2 = _Env.key2;

@override
@EnviedField()
final String key3 = _Env.key3;

@override
@EnviedField()
final int key4 = _Env.key4;

@override
@EnviedField()
final bool key5 = _Env.key5;

@override
@EnviedField()
final Uri key6 = _Env.key6;

@override
@EnviedField()
final DateTime key7 = _Env.key7;

@override
@EnviedField()
final ExampleEnum key8 = _Env.key8;

@override
@EnviedField(rawString: true)
final String key9 = _Env.key9;
}
2 changes: 2 additions & 0 deletions examples/envied_example/lib/release_env.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions packages/envied/lib/src/envied_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,29 @@ final class Envied {
/// ```
final bool useConstantCase;

/// Whether to interpolate the values for all fields.
/// If [interpolate] is `true`, the value will be interpolated
/// with the environment variables.
final bool interpolate;

/// Whether to use the raw string format for all string values.
///
/// **NOTE**: The string is always formatted `'<value>'`.
///
/// If [rawStrings] is `true`, all Strings will be raw formatted `r'<value>'`
/// and the value may not contain a single quote.
/// Escapes single quotes and newlines in the value.
final bool rawStrings;

const Envied({
String? path,
bool? requireEnvFile,
this.name,
this.obfuscate = false,
this.allowOptionalFields = false,
this.useConstantCase = false,
this.interpolate = true,
this.rawStrings = false,
}) : path = path ?? '.env',
requireEnvFile = requireEnvFile ?? false;
}
Expand Down Expand Up @@ -128,11 +144,29 @@ final class EnviedField {
/// ```
final bool? useConstantCase;

/// Whether to use the interpolated value for the field.
/// If [interpolate] is `true`, the value will be interpolated
/// with the environment variables.
final bool? interpolate;

/// Whether to use the raw string format for the value.
///
/// Can only be used with a [String] type.
///
/// **NOTE**: The string is always formatted `'<value>'`.
///
/// If [rawString] is `true`, creates a raw String formatted `r'<value>'`
/// and the value may not contain a single quote.
/// Escapes single quotes and newlines in the value.
final bool? rawString;

const EnviedField({
this.varName,
this.obfuscate,
this.defaultValue,
this.optional,
this.useConstantCase,
this.interpolate,
this.rawString,
});
}
18 changes: 18 additions & 0 deletions packages/envied_generator/lib/src/env_val.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';

/// Represents a raw environment variable as well as its interpolated value.
class EnvVal with EquatableMixin {
const EnvVal({
required this.raw,
String? interpolated,
}) : interpolated = interpolated ?? raw;

final String raw;
final String interpolated;

@override
String toString() => interpolated;

@override
List<Object?> get props => [raw, interpolated];
}
6 changes: 5 additions & 1 deletion packages/envied_generator/lib/src/generate_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Iterable<Field> generateFields(
FieldElement field,
String? value, {
bool allowOptional = false,
bool rawString = false,
}) {
final String type = field.type.getDisplayString(withNullability: false);

Expand Down Expand Up @@ -127,7 +128,10 @@ Iterable<Field> generateFields(
literalString(value),
],
);
} else if (field.type.isDartCoreString || field.type is DynamicType) {
} else if (field.type.isDartCoreString) {
modifier = FieldModifier.constant;
result = literalString(value, raw: rawString);
} else if (field.type is DynamicType) {
modifier = FieldModifier.constant;
result = literalString(value);
} else {
Expand Down
35 changes: 27 additions & 8 deletions packages/envied_generator/lib/src/generator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'dart:io';
import 'dart:io' show Platform;

import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
Expand All @@ -9,6 +9,7 @@ import 'package:code_builder/code_builder.dart';
import 'package:dart_style/dart_style.dart';
import 'package:envied/envied.dart';
import 'package:envied_generator/src/build_options.dart';
import 'package:envied_generator/src/env_val.dart';
import 'package:envied_generator/src/generate_field.dart';
import 'package:envied_generator/src/generate_field_encrypted.dart';
import 'package:envied_generator/src/load_envs.dart';
Expand Down Expand Up @@ -51,9 +52,11 @@ final class EnviedGenerator extends GeneratorForAnnotation<Envied> {
annotation.read('allowOptionalFields').literalValue as bool? ?? false,
useConstantCase:
annotation.read('useConstantCase').literalValue as bool? ?? false,
interpolate: annotation.read('interpolate').literalValue as bool? ?? true,
rawStrings: annotation.read('rawStrings').literalValue as bool? ?? false,
);

final Map<String, String> envs =
final Map<String, EnvVal> envs =
await loadEnvs(config.path, (String error) {
if (config.requireEnvFile) {
throw InvalidGenerationSourceError(
Expand Down Expand Up @@ -91,7 +94,7 @@ final class EnviedGenerator extends GeneratorForAnnotation<Envied> {
static Iterable<Field> _generateFields({
required FieldElement field,
required Envied config,
required Map<String, String> envs,
required Map<String, EnvVal> envs,
}) {
final DartObject? dartObject =
_typeChecker(EnviedField).firstAnnotationOf(field);
Expand All @@ -112,14 +115,15 @@ final class EnviedGenerator extends GeneratorForAnnotation<Envied> {

final Object? defaultValue = reader.read('defaultValue').literalValue;

late final String? varValue;
late final EnvVal? varValue;

if (envs.containsKey(varName)) {
varValue = envs[varName];
} else if (Platform.environment.containsKey(varName)) {
varValue = Platform.environment[varName];
varValue = EnvVal(raw: Platform.environment[varName]!);
} else {
varValue = defaultValue?.toString();
varValue =
defaultValue != null ? EnvVal(raw: defaultValue.toString()) : null;
}

if (field.type is InvalidType) {
Expand All @@ -132,6 +136,12 @@ final class EnviedGenerator extends GeneratorForAnnotation<Envied> {
final bool optional = reader.read('optional').literalValue as bool? ??
config.allowOptionalFields;

final bool interpolate =
reader.read('interpolate').literalValue as bool? ?? config.interpolate;

final bool rawString =
reader.read('rawString').literalValue as bool? ?? config.rawStrings;

// Throw if value is null but the field is not nullable
bool isNullable = field.type is DynamicType ||
field.type.nullabilitySuffix == NullabilitySuffix.question;
Expand All @@ -143,7 +153,16 @@ final class EnviedGenerator extends GeneratorForAnnotation<Envied> {
}

return reader.read('obfuscate').literalValue as bool? ?? config.obfuscate
? generateFieldsEncrypted(field, varValue, allowOptional: optional)
: generateFields(field, varValue, allowOptional: optional);
? generateFieldsEncrypted(
field,
interpolate ? varValue?.interpolated : varValue?.raw,
allowOptional: optional,
)
: generateFields(
field,
interpolate ? varValue?.interpolated : varValue?.raw,
allowOptional: optional,
rawString: rawString,
);
}
}
3 changes: 2 additions & 1 deletion packages/envied_generator/lib/src/load_envs.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'dart:io' show File;

import 'package:envied_generator/src/env_val.dart';
import 'package:envied_generator/src/parser.dart';

/// Load the environment variables from the supplied [path],
/// using the `dotenv` parser.
///
/// If file doesn't exist, an error will be thrown through the
/// [onError] function.
Future<Map<String, String>> loadEnvs(
Future<Map<String, EnvVal>> loadEnvs(
String path,
Function(String) onError,
) async {
Expand Down
Loading

0 comments on commit ca04a3e

Please sign in to comment.