Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
schultek committed Dec 3, 2023
2 parents 1ecad84 + 126caeb commit b794c65
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 17 deletions.
20 changes: 20 additions & 0 deletions packages/dart_mappable/doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ global_options:
generateMethods: [decode, encode, copy, stringify, equals]
```
### `build_extensions`

The `build_extensions` option allows you to specify custom paths for the generated files. This is particularly useful when working with certain code generation scenarios. It takes a map where keys are paths to source files and values are lists of corresponding generated file paths.

#### Example:

Here is an example to write in a build.yaml file to generate the generated files in a `generated` folder:
```yaml
targets:
$default:
builders:
# only to resolve build_runner conflicts
dart_mappable_builder:
options:
build_extensions:
'lib/{{path}}/{{file}}.dart':
- 'lib/{{path}}/generated/{{file}}.mapper.dart'
- 'lib/{{path}}/generated/{{file}}.init.dart'
```

---

<p align="right"><a href="../topics/Copy-With-topic.html">Next: Copy-With</a></p>
10 changes: 9 additions & 1 deletion packages/dart_mappable_builder/lib/src/builder_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import 'package:dart_mappable/dart_mappable.dart';

import 'utils.dart';

const _defaultExtensions = {
'.dart': ['.mapper.dart', '.init.dart']
};

/// The builder options for a specific library
class MappableOptions {
final CaseStyle? caseStyle;
Expand All @@ -13,6 +17,7 @@ class MappableOptions {
final InitializerScope? initializerScope;
final int? lineLength;
final Map<String, String> renameMethods;
final Map<String, List<String>> buildExtensions;

MappableOptions({
this.caseStyle,
Expand All @@ -23,6 +28,7 @@ class MappableOptions {
this.initializerScope,
this.lineLength,
this.renameMethods = const {},
this.buildExtensions = _defaultExtensions,
});

MappableOptions.parse(Map options)
Expand All @@ -36,7 +42,9 @@ class MappableOptions {
initializerScope = null,
lineLength =
options['lineLength'] as int? ?? options['line_length'] as int?,
renameMethods = toMap(options['renameMethods'] ?? {});
renameMethods = toMap(options['renameMethods'] ?? {}),
buildExtensions =
validatedBuildExtensionsFrom(options, _defaultExtensions);

MappableOptions apply(MappableOptions? options, {bool forceJoin = true}) {
if (options == null) return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ class MappableBuilder implements Builder {
}

@override
Map<String, List<String>> get buildExtensions => const {
'.dart': ['.mapper.dart', '.init.dart']
};
Map<String, List<String>> get buildExtensions => options.buildExtensions;

Future<MapperElementGroup> createMapperGroup(BuildStep buildStep) async {
var entryLib = await buildStep.inputLibrary;
Expand Down Expand Up @@ -95,16 +93,20 @@ class MappableBuilder implements Builder {

var output = await Future.wait(generators.map((g) => g.generate()));

final outputId = buildStep.allowedOutputs.first;

var libraryPath =
p.relative(buildStep.inputId.path, from: p.dirname(outputId.path));
var source = DartFormatter(pageWidth: options.lineLength ?? 80).format(
'// coverage:ignore-file\n'
'// GENERATED CODE - DO NOT MODIFY BY HAND\n'
'// ignore_for_file: type=lint\n'
'// ignore_for_file: unused_element, unnecessary_cast\n'
'// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter\n\n'
'part of \'${p.basename(buildStep.inputId.uri.toString())}\';\n\n'
'part of \'$libraryPath\';\n\n'
'${output.join('\n\n')}\n' //,
);
var outputId = buildStep.inputId.changeExtension('.mapper.dart');

await buildStep.writeAsString(outputId, source);
}

Expand All @@ -123,9 +125,12 @@ class MappableBuilder implements Builder {

discovered.sortBy((e) => e.key.source.uri.toString());

final outputId = buildStep.allowedOutputs.last;

output.write(writeImports(
buildStep.inputId,
discovered.map((e) => e.key.source.uri).toList(),
p.dirname(outputId.uri.path),
));

output.write('void initializeMappers() {\n');
Expand All @@ -146,12 +151,11 @@ class MappableBuilder implements Builder {
'${output.toString()}\n',
);

var outputId = buildStep.inputId.changeExtension('.init.dart');
await buildStep.writeAsString(outputId, source);
}
}

String writeImports(AssetId input, List<Uri> imports) {
String writeImports(AssetId input, List<Uri> imports, String outputDirectory) {
List<String> package = [], relative = [];
var prefixes = <String, int?>{};

Expand All @@ -160,21 +164,14 @@ String writeImports(AssetId input, List<Uri> imports) {
for (var i = 0; i < imports.length; i++) {
var import = imports[i];
if (import.isScheme('asset')) {
var relativePath =
path.relative(import.path, from: path.dirname(input.uri.path));
var relativePath = path.relative(import.path, from: outputDirectory);

relative.add(relativePath);
prefixes[relativePath] = i;
} else if (import.isScheme('package') &&
import.pathSegments.first == input.package &&
input.pathSegments.first == 'lib') {
var libPath =
import.replace(pathSegments: import.pathSegments.skip(1)).path;

var inputPath =
input.uri.replace(pathSegments: input.uri.pathSegments.skip(1)).path;

var relativePath = path.relative(libPath, from: path.dirname(inputPath));
var relativePath = path.relative(import.path, from: outputDirectory);

relative.add(relativePath);
prefixes[relativePath] = i;
Expand Down
49 changes: 49 additions & 0 deletions packages/dart_mappable_builder/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,52 @@ extension ObjectReader on DartObject {
return result;
}
}

Map<String, List<String>> validatedBuildExtensionsFrom(
Map? optionsMap,
Map<String, List<String>> defaultExtensions,
) {
final extensionsOption = optionsMap?.remove('build_extensions');
if (extensionsOption == null) {
// defaultExtensions are provided by the builder author, not the end user.
// It should be safe to skip validation.
return defaultExtensions;
}

if (extensionsOption is! Map) {
throw ArgumentError(
'Configured build_extensions should be a map from inputs to outputs.',
);
}

final result = <String, List<String>>{};

for (final entry in extensionsOption.entries) {
final input = entry.key;
if (input is! String || !input.endsWith('.dart')) {
throw ArgumentError(
'Invalid key in build_extensions option: `$input` '
'should be a string ending with `.dart`',
);
}

final output = (entry.value is List) ? entry.value as List : [entry.value];

for (var i = 0; i < output.length; i++) {
final o = output[i];
if (o is! String || (i == 0 && !o.endsWith('.dart'))) {
throw ArgumentError(
'Invalid output extension `${entry.value}`. It should be a string '
'or a list of strings with the first ending with `.dart`',
);
}
}

result[input] = output.cast<String>().toList();
}

if (result.isEmpty) {
throw ArgumentError('Configured build_extensions must not be empty.');
}
return result;
}

0 comments on commit b794c65

Please sign in to comment.