merging_builder 0.0.8 merging_builder: ^0.0.8 copied to clipboard
Generic Dart builder and generator that reads several input files and writes merged output to a stand-alone file. Has support for specifying a header and footer.
Merging Builder #
Introduction #
Source code generation has become an integral software development tool when building and maintaining a large number of data models, data access object, widgets, etc.
The library merging_builder
provides a Dart builder that reads several input files and writes merged output to one output file. The builder has support for specifying a header and footer to be placed at the top and bottom of the output file.
A conventional builder typically calls the generator method generate
from within its build
method to retrieve the generated source-code. MergingBuilder
(indirectly) calls the MergingGenerator
method generateStream
. It allows the generator to pass a stream of data of type T
to the builder, one stream item for each annotated element passed to the generator method generateStreamItemForAnnotatedElement
.
The private builder method _combineStreams
combines the streams received for each processed file asset and calls the generator method generateMergedContent
. As a result, this method has access to all stream items of type T
generated for each annotated element in each input file. It is the task of this method to generate the merged source-code output.
The figure below shows the flow of data between the builder and the generator. The data type is indicated by the starting point of the orange connectors. Dotted connectors represent a stream of data.
Usage #
Following the example of source_gen
, it is common practice to separate builders and generators from the code using those builders.
In the example provided with this library, the package defining a new builder is called researcher_builder
and the package using this builder is called researcher
. To set up a build system the following steps are required:
-
Include
merging_builder
andbuild
as dependencies in the filepubspec.yaml
of the package defining the builder. (In the example mentioned here, the generator also requires the packagesanalyzer
andsource_gen
.) -
In the package defining the custom builder, create a custom generator that extends
MergingGenerator
. Users will have to implement the methodsgenerateItemForAnnotatedElement
andgenerateMergedContent
. In the example shown belowgenerateItemForAnnotatedElement
reads a list of strings whilegenerateMergedContent
merges the data and generates output that is written to researchers.dart.Show details.
import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/src/builder/build_step.dart'; import 'package:merging_builder/merging_builder.dart'; import 'package:merging_builder/src/annotations/add_names.dart'; import 'package:source_gen/source_gen.dart'; import 'package:quote_buffer/quote_buffer.dart'; /// Reads numbers from annotated classes and emits the sum. class AddNamesGenerator extends MergingGenerator<List<String>, AddNames> { /// Portion of source code included at the top of the generated file. /// Should be specified as header when constructing the merging builder. static String get header { return '/// Added names.'; } /// Portion of source code included at the very bottom of the generated file. /// Should be specified as [footer] when constructing the merging builder. static String get footer { return '/// This is the footer.'; } @override List<String> generateStreamItemForAnnotatedElement( Element element, ConstantReader annotation, BuildStep buildStep, ) { final List<String> result = []; if (element is ClassElement) { final nameObjects = element.getField('names')?.computeConstantValue()?.toListValue(); if (nameObjects != null) { for (final nameObj in nameObjects) { result.add(nameObj.toStringValue()); } return result; } } return null; } /// Returns merged content. @override FutureOr<String> generateMergedContent(Stream<List<String>> stream) async { final b = QuoteBuffer(); int i = 0; final List<List<String>> allNames = []; // Iterate over stream: await for (final names in stream) { b.write('final name$i = ['); b.writelnAllQ(names, separator2: ','); b.writeln('];'); ++i; allNames.add(names); } b.writeln(''); b.writeln('final List<List<String>> names = ['); for (var names in allNames) { b.writeln(' ['); b.writelnAllQ(names, separator2: ','); b.writeln(' ],'); } b.writeln('];'); return b.toString(); } }
-
Create an instance of
MergingBuilder
. Following the example ofsource_gen
, builders are typically placed in a file called:builder.dart
located in thelib
folder of the builder package. The generatorAddNamesGenerator
extendsMergingGenerator<List<String>, AddNames>
(see step 2). Input sources may be specified using wildecard characters supported byGlob
.import 'package:build/build.dart'; import 'package:merging_builder/merging_builder.dart'; import 'generators/add_names_generator.dart'; Builder addNamesBuilder(BuilderOptions options) => MergingBuilder<List<String>>( generator: AddNamesGenerator(), inputFiles: 'lib/input/*.dart', outputFile: 'lib/researchers.dart', header: AddNamesGenerator.header, footer: AddNamesGenerator.footer, sortAssets: false, );
-
In the package defining the builder, add the builder configuration for the builder
add_names_builder
(see below). The build extensions forMergingBuilder
must be specified using the notation available for synthetic input. For example,"$lib$"
indicates that the input files are located in the folderlib
or a subfolder thereof. For more information consult the section: Writing a Builder using a synthetic input found in the documentation of the Dart packagebuild
.builders: add_names_builder: import: "package:researcher_builder/builder.dart" builder_factories: ["addNamesBuilder"] build_extensions: {"$lib$": ["*.dart"]} auto_apply: root_package build_to: source builders:
-
In the package using the custom builder,
researcher
, addadd_names_builder
to the list of known builders. The filebuild.yaml
is shown below.targets: $default: builders: # Configure the builder `pkg_name|builder_name` researcher_builder|add_names_builder: enabled: true # Only run this builder on the specified input. # generate_for: # - lib/*.dart
-
In the package using the builder,
researcher
, addresearcher_builder
andbuild_runner
as dev_dependencies in the filepubspec.yaml
.name: researcher description: Example demonstrating how to use the library merging_builder. environment: sdk: '>=2.8.1 <3.0.0' dev_dependencies: build_runner: ^1.10.0 researcher_builder: path: ../researcher_builder
-
Initiate the build process by using the command:
# pub run build_runner build --delete-conflicting-outputs --verbose
Examples #
For further information on how to use MergingBuilder
see example.
Features and bugs #
Please file feature requests and bugs at the issue tracker.