├── example ├── simple_transformer │ ├── lib │ │ ├── test.txt │ │ └── transformer.dart │ ├── README.md │ └── pubspec.yaml ├── markdown_converter │ ├── lib │ │ ├── test2.md │ │ ├── images │ │ │ └── bison.jpg │ │ ├── index.markdown │ │ └── transformer.dart │ ├── README.md │ └── pubspec.yaml ├── aggregate_transformer │ ├── README.md │ ├── pubspec.yaml │ └── lib │ │ ├── recipes-grammas │ │ ├── winter-squash-pie-recipe.html │ │ └── banana-pudding-recipe.html │ │ └── transformer.dart └── lazy_transformer │ ├── pubspec.yaml │ ├── lib │ ├── message.txt │ └── transformer.dart │ └── README.md ├── .analysis_options ├── codereview.settings ├── .gitignore ├── test ├── transformer │ ├── lazy_rewrite.dart │ ├── lazy_assets.dart │ ├── emit_nothing.dart │ ├── create_asset.dart │ ├── lazy_aggregate_many_to_many.dart │ ├── lazy_aggregate_many_to_one.dart │ ├── lazy_many_to_one.dart │ ├── declaring_aggregate_many_to_many.dart │ ├── lazy_check_content_and_rename.dart │ ├── declaring_rewrite.dart │ ├── declaring_aggregate_many_to_one.dart │ ├── bad_log.dart │ ├── check_content.dart │ ├── conditionally_consume_primary.dart │ ├── bad.dart │ ├── declaring_check_content_and_rename.dart │ ├── lazy_bad.dart │ ├── has_input.dart │ ├── sync_rewrite.dart │ ├── one_to_many.dart │ ├── aggregate_many_to_many.dart │ ├── catch_asset_not_found.dart │ ├── log.dart │ ├── rewrite.dart │ ├── check_content_and_rename.dart │ ├── declaring_bad.dart │ ├── declare_assets.dart │ ├── many_to_one.dart │ └── aggregate_many_to_one.dart ├── barback_mode_test.dart ├── package_graph │ ├── many_parallel_transformers_test.dart │ ├── repetition_test.dart │ ├── get_all_assets_test.dart │ ├── source_test.dart │ └── transform │ │ └── consume_input_test.dart ├── static_provider_test.dart ├── cancelable_future_test.dart ├── too_many_open_files_test.dart ├── logger_test.dart ├── transformer_test.dart ├── asset_id_test.dart ├── multiset_test.dart ├── stream_replayer_test.dart └── asset_set_test.dart ├── README.md ├── lib ├── src │ ├── transformer │ │ ├── lazy_transformer.dart │ │ ├── lazy_aggregate_transformer.dart │ │ ├── declaring_transformer.dart │ │ ├── transformer_group.dart │ │ ├── declaring_aggregate_transformer.dart │ │ ├── barback_settings.dart │ │ ├── aggregate_transformer.dart │ │ ├── declaring_transform.dart │ │ ├── transform_logger.dart │ │ ├── wrapping_aggregate_transformer.dart │ │ ├── transformer.dart │ │ ├── declaring_aggregate_transform.dart │ │ ├── base_transform.dart │ │ ├── transform.dart │ │ └── aggregate_transform.dart │ ├── internal_asset.dart │ ├── log.dart │ ├── asset │ │ ├── asset_node_set.dart │ │ ├── asset_forwarder.dart │ │ ├── asset.dart │ │ ├── asset_set.dart │ │ ├── asset_id.dart │ │ └── internal_asset.dart │ ├── utils │ │ ├── cancelable_future.dart │ │ ├── multiset.dart │ │ ├── file_pool.dart │ │ ├── stream_replayer.dart │ │ └── stream_pool.dart │ ├── package_provider.dart │ ├── graph │ │ ├── node_status.dart │ │ ├── node_streams.dart │ │ ├── group_runner.dart │ │ ├── static_asset_cascade.dart │ │ ├── phase_output.dart │ │ ├── phase_forwarder.dart │ │ └── transformer_classifier.dart │ ├── build_result.dart │ ├── serialize.dart │ └── barback.dart └── barback.dart ├── .status ├── pubspec.yaml └── LICENSE /example/simple_transformer/lib/test.txt: -------------------------------------------------------------------------------- 1 | ABC 2 | -------------------------------------------------------------------------------- /.analysis_options: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: true 3 | -------------------------------------------------------------------------------- /example/markdown_converter/lib/test2.md: -------------------------------------------------------------------------------- 1 | 2 | # Another first level header 3 | 4 | * _Italics_ 5 | * `literal` 6 | * **bold** 7 | 8 | -------------------------------------------------------------------------------- /example/markdown_converter/lib/images/bison.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-archive/barback/master/example/markdown_converter/lib/images/bison.jpg -------------------------------------------------------------------------------- /codereview.settings: -------------------------------------------------------------------------------- 1 | CODE_REVIEW_SERVER: http://codereview.chromium.org/ 2 | VIEW_VC: https://github.com/dart-lang/barback/commit/ 3 | CC_LIST: reviews@dartlang.org -------------------------------------------------------------------------------- /example/markdown_converter/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to write a simple pub transformer. 2 | 3 | For more information, see Writing a Pub Transformer: 4 | https://www.dartlang.org/tools/pub/transformers/ 5 | -------------------------------------------------------------------------------- /example/simple_transformer/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to write a simple pub transformer. 2 | 3 | For more information, see Writing a Pub Transformer: 4 | https://www.dartlang.org/tools/pub/transformers/ 5 | -------------------------------------------------------------------------------- /example/aggregate_transformer/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to write an aggregate transformer. 2 | 3 | For more information, see Writing an Aggregate Transformer at: 4 | https://www.dartlang.org/tools/pub/transformers/aggregate.html 5 | -------------------------------------------------------------------------------- /example/markdown_converter/lib/index.markdown: -------------------------------------------------------------------------------- 1 | 2 | # First level header 3 | 4 | * one 5 | * two 6 | * three 7 | 8 | ## Second level header 9 | 10 | 1. apple 11 | 2. banana 12 | 3. pear 13 | 14 | The Google Bison 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .buildlog 3 | .pub/ 4 | build/ 5 | packages 6 | .packages 7 | 8 | # Or the files created by dart2js. 9 | *.dart.js 10 | *.js_ 11 | *.js.deps 12 | *.js.map 13 | 14 | # Include when developing application packages. 15 | pubspec.lock -------------------------------------------------------------------------------- /example/lazy_transformer/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: lazy_transformer 2 | description: This example implements a very simple lazy transformer 3 | which implements a ROT13 converter. ROT13 (ROTate by 13 spaces) is 4 | a classic substitution cipher where each letter is replaced by 5 | the letter 13 places later in the alphabet. 6 | 7 | dependencies: 8 | barback: ">=0.14.1 <0.16.0" 9 | 10 | transformers: 11 | - lazy_transformer 12 | -------------------------------------------------------------------------------- /example/aggregate_transformer/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: aggregate_transformer 2 | description: This example implements an aggregate transformer. 3 | It collects recipes, stored as incomplete HTML files, into 4 | a single, complete HTML file. 5 | 6 | dependencies: 7 | barback: ">=0.14.1 <0.16.0" 8 | 9 | # Override the barback dependency so this example always uses the version 10 | # of barback it's bundled with. 11 | dependency_overrides: 12 | barback: {path: ../..} 13 | 14 | transformers: 15 | - aggregate_transformer 16 | -------------------------------------------------------------------------------- /example/simple_transformer/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: simple_transformer 2 | description: This hello world example implements a simple 3 | transformer that inserts a copyright string into 4 | an input asset - a file that ends with ".txt". 5 | 6 | dependencies: 7 | barback: ">=0.14.1 <0.16.0" 8 | 9 | # Override the barback dependency so this example always uses the version 10 | # of barback it's bundled with. 11 | dependency_overrides: 12 | barback: {path: ../..} 13 | 14 | transformers: 15 | - simple_transformer 16 | -------------------------------------------------------------------------------- /example/markdown_converter/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: markdown_converter 2 | description: This hello world example implements a simple 3 | transformer that converts a markdown file (with 4 | a ".mdown", ".md", or a ".markdown" extension) to HTML. 5 | 6 | dependencies: 7 | barback: ">=0.14.1 <0.16.0" 8 | markdown: any 9 | 10 | # Override the barback dependency so this example always uses the version 11 | # of barback it's bundled with. 12 | dependency_overrides: 13 | barback: {path: ../..} 14 | 15 | transformers: 16 | - markdown_converter 17 | -------------------------------------------------------------------------------- /example/lazy_transformer/lib/message.txt: -------------------------------------------------------------------------------- 1 | Dear Mom, 2 | 3 | Hi! How are you? Sorry that I haven't written recently, 4 | but I hope all is well! 5 | 6 | Life is great at University. I'm sure that I can bring 7 | up my D- in French. 8 | 9 | Also, the Dean's office says they won't press charges. 10 | It was a big todo about nothing - just your typical college 11 | prank and the badger (UFE's mascot) was eating pretty 12 | well on my lunch card. And some spackle will fix the 13 | damage to my dorm room walls right up... 14 | 15 | By the way, can I have a loan? $300 would be great. 16 | 17 | Thanks so much and I love you! 18 | 19 | Your loving son, 20 | 21 | Rodger 22 | -------------------------------------------------------------------------------- /test/transformer/lazy_rewrite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_rewrite; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'declaring_rewrite.dart'; 10 | 11 | /// Like [RewriteTransformer], but returns a lazy asset that doesn't perform the 12 | /// rewrite until it's materialized. 13 | class LazyRewriteTransformer extends DeclaringRewriteTransformer 14 | implements LazyTransformer { 15 | LazyRewriteTransformer(String from, String to) : super(from, to); 16 | } 17 | -------------------------------------------------------------------------------- /test/transformer/lazy_assets.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_assets; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'declare_assets.dart'; 10 | 11 | /// Like [DeclareAssetsTransformer], but lazy. 12 | class LazyAssetsTransformer extends DeclareAssetsTransformer 13 | implements LazyTransformer { 14 | LazyAssetsTransformer(Iterable declared, 15 | {Iterable emitted, String input}) 16 | : super(declared, emitted: emitted, input: input); 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DEPRECATED** 2 | 3 | The [pub][] transformer system will be removed in Dart 2. 4 | See the [Dart 2 Migration Guide](https://webdev.dartlang.org/dart-2) for 5 | guidance. 6 | 7 | --- 8 | 9 | Barback is an asset build system. It is the library underlying 10 | [pub][]'s asset transformers in 11 | `pub build` and `pub serve`. 12 | 13 | Given a set of input files and a set of transformations (think compilers, 14 | preprocessors and the like), it will automatically apply the appropriate 15 | transforms and generate output files. When inputs are modified, it automatically 16 | runs the transforms that are affected. 17 | 18 | To learn more, see [here][]. 19 | 20 | [pub]: https://www.dartlang.org/tools/pub/get-started 21 | [here]: https://www.dartlang.org/tools/pub/assets-and-transformers 22 | -------------------------------------------------------------------------------- /test/transformer/emit_nothing.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.emit_nothing; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that emits no assets. 12 | class EmitNothingTransformer extends MockTransformer { 13 | final String extension; 14 | 15 | EmitNothingTransformer(this.extension); 16 | 17 | bool doIsPrimary(AssetId id) => id.extension == ".$extension"; 18 | 19 | void doApply(Transform transform) { 20 | // Emit nothing. 21 | } 22 | 23 | String toString() => "$extension->nothing"; 24 | } 25 | -------------------------------------------------------------------------------- /test/transformer/create_asset.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.create_asset; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that outputs an asset with the given id. 12 | class CreateAssetTransformer extends MockTransformer { 13 | final String output; 14 | 15 | CreateAssetTransformer(this.output); 16 | 17 | bool doIsPrimary(AssetId id) => true; 18 | 19 | void doApply(Transform transform) { 20 | transform 21 | .addOutput(new Asset.fromString(new AssetId.parse(output), output)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/lazy_transformer/README.md: -------------------------------------------------------------------------------- 1 | This example shows how to write a lazy transformer. 2 | 3 | This lazy tranformer implements a ROT13 converter. ROT13 (ROTate 13) 4 | is a classic substitution cipher which replaces each letter in the source 5 | file with the corresponding letter 13 places later in the alphabet. 6 | The source file should have a ".txt" extension and the converted file 7 | is created with a ".shhhh" extension. 8 | 9 | Generally, only transformers that take a long time to run should be made lazy. 10 | This transformer is not particularly slow, but imagine that it might be used 11 | to convert the entire library of congress–laziness would then be a virtue. 12 | 13 | For more information, see Writing a Lazy Transformer at: 14 | https://www.dartlang.org/tools/pub/transformers/lazy-transformer.html 15 | -------------------------------------------------------------------------------- /test/transformer/lazy_aggregate_many_to_many.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_aggregate_many_to_many; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'declaring_aggregate_many_to_many.dart'; 10 | 11 | /// An [AggregateTransformer] that takes all assets in each directory with a 12 | /// given extension and adds to their contents. 13 | class LazyAggregateManyToManyTransformer 14 | extends DeclaringAggregateManyToManyTransformer 15 | implements LazyAggregateTransformer { 16 | LazyAggregateManyToManyTransformer(String extension) : super(extension); 17 | } 18 | -------------------------------------------------------------------------------- /test/barback_mode_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.barback_mode_test; 6 | 7 | import 'package:barback/barback.dart'; 8 | import 'package:unittest/unittest.dart'; 9 | 10 | import 'utils.dart'; 11 | 12 | main() { 13 | initConfig(); 14 | test("constructor uses canonical instances for DEBUG and RELEASE", () { 15 | expect( 16 | identical(BarbackMode.DEBUG, new BarbackMode(BarbackMode.DEBUG.name)), 17 | isTrue); 18 | expect( 19 | identical( 20 | BarbackMode.RELEASE, new BarbackMode(BarbackMode.RELEASE.name)), 21 | isTrue); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/transformer/lazy_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.lazy_transformer; 6 | 7 | import 'declaring_transformer.dart'; 8 | 9 | /// An interface for [Transformer]s that indicates that the transformer's 10 | /// outputs shouldn't be generated until requested. 11 | /// 12 | /// The [declareOutputs] method is used to figure out which assets should be 13 | /// treated as "lazy." Lazy assets will only be forced to be generated if 14 | /// they're requested by the user or if they're used by a non-declaring 15 | /// transformer. 16 | abstract class LazyTransformer extends DeclaringTransformer {} 17 | -------------------------------------------------------------------------------- /lib/src/internal_asset.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This library exists so that pub can retain backwards-compatibility with 6 | /// versions of barback prior to 0.13.1. 7 | /// 8 | /// In 0.13.1, `lib/src/internal_asset.dart` was moved to 9 | /// `lib/src/asset/internal_asset.dart. Pub needs to support all versions of 10 | /// barback back through 0.13.0, though. In order for this to work, it needs to 11 | /// be able to import "package:barback/src/internal_asset.dart" on all available 12 | /// barback versions, hence the existence of this library. 13 | library barback.internal_asset; 14 | 15 | export 'asset/internal_asset.dart'; 16 | -------------------------------------------------------------------------------- /test/transformer/lazy_aggregate_many_to_one.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_aggregate_many_to_one; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'declaring_aggregate_many_to_one.dart'; 10 | 11 | /// Like [AggregateManyToOneTransformer], but returns a lazy asset that doesn't 12 | /// perform the rewrite until it's materialized. 13 | class LazyAggregateManyToOneTransformer 14 | extends DeclaringAggregateManyToOneTransformer 15 | implements LazyAggregateTransformer { 16 | LazyAggregateManyToOneTransformer(String extension, String output) 17 | : super(extension, output); 18 | } 19 | -------------------------------------------------------------------------------- /.status: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | # for details. All rights reserved. Use of this source code is governed by a 3 | # BSD-style license that can be found in the LICENSE file. 4 | 5 | # Skip non-test files ending with "_test". 6 | packages/*: Skip 7 | */packages/*: Skip 8 | */*/packages/*: Skip 9 | */*/*/packages/*: Skip 10 | */*/*/*packages/*: Skip 11 | */*/*/*/*packages/*: Skip 12 | 13 | # Only run tests from the build directory, since we don't care about the 14 | # difference between transformed an untransformed code. 15 | test/*: Skip 16 | 17 | [ $runtime == vm && $mode == debug] 18 | build/test/package_graph/repetition_test: Skip # Times out 19 | 20 | [ $runtime == vm && $arch == simarm ] 21 | build/test/too_many_open_files_test: Skip # 14220 22 | 23 | [ $browser ] 24 | *: Fail, OK # Uses dart:io. 25 | -------------------------------------------------------------------------------- /test/transformer/lazy_many_to_one.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_many_to_one; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'many_to_one.dart'; 10 | 11 | /// Like [ManyToOneTransformer], but returns a lazy asset that doesn't perform 12 | /// the conglomeration until it's materialized. 13 | class LazyManyToOneTransformer extends ManyToOneTransformer 14 | implements LazyTransformer { 15 | LazyManyToOneTransformer(String extension) : super(extension); 16 | 17 | void declareOutputs(DeclaringTransform transform) { 18 | transform.declareOutput(transform.primaryId.changeExtension(".out")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/transformer/lazy_aggregate_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.lazy_aggregate_transformer; 6 | 7 | import 'declaring_aggregate_transformer.dart'; 8 | 9 | /// An interface for [AggregateTransformer]s that indicates that the 10 | /// transformer's outputs shouldn't be generated until requested. 11 | /// 12 | /// The [declareOutputs] method is used to figure out which assets should be 13 | /// treated as "lazy." Lazy assets will only be forced to be generated if 14 | /// they're requested by the user or if they're used by a non-declaring 15 | /// transformer. 16 | abstract class LazyAggregateTransformer extends DeclaringAggregateTransformer {} 17 | -------------------------------------------------------------------------------- /example/aggregate_transformer/lib/recipes-grammas/winter-squash-pie-recipe.html: -------------------------------------------------------------------------------- 1 | 2 |

Winter Squash Pie

3 | 14 |

15 | Mix eggs and sugar together. Add pumpkin and mix. Add salt and spices. 16 | Slowly stir in evaporated milk, and then the cream.

17 |

18 | Pour into un nbaked pie crust. Bake in a preheated 425°F oven 19 | for 15 minutes. Turn heat down to 325°F and bake another 45 minutes 20 | until center is firm.

21 |

22 | Remove from oven and cool on a rack before serving.

23 |

24 | Serve with whipped cream.

25 | -------------------------------------------------------------------------------- /test/transformer/declaring_aggregate_many_to_many.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declaring_aggregate_many_to_many; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'aggregate_many_to_many.dart'; 12 | 13 | /// Like [AggregateManyToManyTransformer], but declares its assets ahead of 14 | /// time. 15 | class DeclaringAggregateManyToManyTransformer 16 | extends AggregateManyToManyTransformer 17 | implements DeclaringAggregateTransformer { 18 | DeclaringAggregateManyToManyTransformer(String extension) : super(extension); 19 | 20 | Future declareOutputs(DeclaringAggregateTransform transform) => 21 | transform.primaryIds.asyncMap(transform.declareOutput).toList(); 22 | } 23 | -------------------------------------------------------------------------------- /test/transformer/lazy_check_content_and_rename.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_check_content_and_rename; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'declaring_check_content_and_rename.dart'; 10 | 11 | class LazyCheckContentAndRenameTransformer 12 | extends DeclaringCheckContentAndRenameTransformer 13 | implements LazyTransformer { 14 | LazyCheckContentAndRenameTransformer( 15 | {String oldExtension, 16 | String oldContent, 17 | String newExtension, 18 | String newContent}) 19 | : super( 20 | oldExtension: oldExtension, 21 | oldContent: oldContent, 22 | newExtension: newExtension, 23 | newContent: newContent); 24 | } 25 | -------------------------------------------------------------------------------- /test/transformer/declaring_rewrite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declaring_rewrite; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'rewrite.dart'; 10 | 11 | /// Like [RewriteTransformer], but declares its assets ahead of time. 12 | class DeclaringRewriteTransformer extends RewriteTransformer 13 | implements DeclaringTransformer { 14 | DeclaringRewriteTransformer(String from, String to) : super(from, to); 15 | 16 | void declareOutputs(DeclaringTransform transform) { 17 | if (consumePrimary) transform.consumePrimary(); 18 | for (var extension in to.split(" ")) { 19 | var id = transform.primaryId.changeExtension(".$extension"); 20 | transform.declareOutput(id); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/transformer/declaring_aggregate_many_to_one.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declaring_aggregate_many_to_one; 6 | 7 | import 'package:barback/barback.dart'; 8 | import 'package:path/path.dart' as path; 9 | 10 | import 'aggregate_many_to_one.dart'; 11 | 12 | /// Like [AggregateManyToOneTransformer], but declares its assets ahead of time. 13 | class DeclaringAggregateManyToOneTransformer 14 | extends AggregateManyToOneTransformer 15 | implements DeclaringAggregateTransformer { 16 | DeclaringAggregateManyToOneTransformer(String extension, String output) 17 | : super(extension, output); 18 | 19 | void declareOutputs(DeclaringAggregateTransform transform) { 20 | transform.declareOutput( 21 | new AssetId(transform.package, path.url.join(transform.key, output))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/transformer/bad_log.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.bad_log; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that logs an error when run, Before generating the given 12 | /// outputs. 13 | class BadLogTransformer extends MockTransformer { 14 | /// The list of asset names that it should output. 15 | final List outputs; 16 | 17 | BadLogTransformer(this.outputs); 18 | 19 | bool doIsPrimary(AssetId id) => true; 20 | 21 | void doApply(Transform transform) { 22 | transform.logger.error("first error"); 23 | transform.logger.error("second error"); 24 | 25 | for (var output in outputs) { 26 | var id = new AssetId.parse(output); 27 | transform.addOutput(new Asset.fromString(id, output)); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/transformer/check_content.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.check_content; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A transformer that modifies assets that contains the given content. 14 | class CheckContentTransformer extends MockTransformer { 15 | final Pattern content; 16 | final String addition; 17 | 18 | CheckContentTransformer(this.content, this.addition); 19 | 20 | bool doIsPrimary(AssetId id) => true; 21 | 22 | Future doApply(Transform transform) { 23 | return getPrimary(transform).then((primary) { 24 | return primary.readAsString().then((value) { 25 | if (!value.contains(content)) return; 26 | 27 | transform 28 | .addOutput(new Asset.fromString(primary.id, "$value$addition")); 29 | }); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/simple_transformer/lib/transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:barback/barback.dart'; 6 | 7 | import 'dart:async'; 8 | 9 | class InsertCopyright extends Transformer { 10 | String copyright = "Copyright (c) 2014, the Example project authors.\n"; 11 | 12 | // A constructor named "asPlugin" is required. It can be empty, but 13 | // it must be present. It is how pub determines that you want this 14 | // class to be publicly available as a loadable transformer plugin. 15 | InsertCopyright.asPlugin(); 16 | 17 | Future isPrimary(AssetId id) async => id.extension == '.txt'; 18 | 19 | Future apply(Transform transform) async { 20 | var content = await transform.primaryInput.readAsString(); 21 | var id = transform.primaryInput.id; 22 | var newContent = copyright + content; 23 | transform.addOutput(new Asset.fromString(id, newContent)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/transformer/conditionally_consume_primary.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.conditionally_consume_primary; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'rewrite.dart'; 12 | 13 | /// A transformer that consumes its primary input only if its contents match a 14 | /// given pattern. 15 | class ConditionallyConsumePrimaryTransformer extends RewriteTransformer { 16 | final Pattern content; 17 | 18 | ConditionallyConsumePrimaryTransformer(String from, String to, this.content) 19 | : super(from, to); 20 | 21 | Future doApply(Transform transform) { 22 | return getPrimary(transform).then((primary) { 23 | return primary.readAsString().then((value) { 24 | if (value.contains(content)) transform.consumePrimary(); 25 | return super.doApply(transform); 26 | }); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/transformer/bad.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.bad; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that throws an exception when run, after generating the 12 | /// given outputs. 13 | class BadTransformer extends MockTransformer { 14 | /// The error it throws. 15 | static const ERROR = "I am a bad transformer!"; 16 | 17 | /// The list of asset names that it should output. 18 | final List outputs; 19 | 20 | BadTransformer(this.outputs); 21 | 22 | bool doIsPrimary(AssetId id) => true; 23 | 24 | void doApply(Transform transform) { 25 | // Create the outputs first. 26 | for (var output in outputs) { 27 | var id = new AssetId.parse(output); 28 | transform.addOutput(new Asset.fromString(id, output)); 29 | } 30 | 31 | // Then fail. 32 | throw ERROR; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/transformer/declaring_check_content_and_rename.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declaring_check_content_and_rename; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'check_content_and_rename.dart'; 10 | 11 | class DeclaringCheckContentAndRenameTransformer 12 | extends CheckContentAndRenameTransformer implements DeclaringTransformer { 13 | DeclaringCheckContentAndRenameTransformer( 14 | {String oldExtension, 15 | String oldContent, 16 | String newExtension, 17 | String newContent}) 18 | : super( 19 | oldExtension: oldExtension, 20 | oldContent: oldContent, 21 | newExtension: newExtension, 22 | newContent: newContent); 23 | 24 | void declareOutputs(DeclaringTransform transform) { 25 | transform 26 | .declareOutput(transform.primaryId.changeExtension('.$newExtension')); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/aggregate_transformer/lib/recipes-grammas/banana-pudding-recipe.html: -------------------------------------------------------------------------------- 1 | 2 |

Banana Pudding

3 |
    4 |
  • 1/4 cup water
  • 5 |
  • 2 Tblsp flour
  • 6 |
  • 1 cup sugar
  • 7 |
  • 6 lg eggs
  • 8 |
  • 1 can evaporated milk
  • 9 |
  • 1 Tblsp vanilla extract
  • 10 |
  • 16 oz sour cream
  • 11 |
  • 3 large bananas, sliced
  • 12 |
  • 1 (16 oz) package of vanilla wafers
  • 13 |
14 |

15 | Add water to saucepan. Whisk in flour until smooth. Add sugar 16 | and mix well. Add eggs, one at a time, mixing well after each one. 17 | Slowly stir in milk, mixing well.

18 |

19 | Place pan on low heat. Cook, stirring constantly, until mixture 20 | is thickened to the consistency of a thick gravy, approximately 21 | 20 minutes. The mixture will start to steam and produce bubbles, 22 | but you don't want a full boil.

23 |

24 | Remove from heat and stir in vanilla. Cool thoroughly. Fold in 25 | sour cream and mix well.

26 |

27 | Layer wafers, bananas, and pudding in a glass serving bowl, such 28 | as a trifle bowl. Thoroughly chill. Serve with whipped cream.

29 | -------------------------------------------------------------------------------- /test/transformer/lazy_bad.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.lazy_bad; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A lazy transformer that throws an exception after [declareOutputs]. 12 | class LazyBadTransformer extends MockTransformer implements LazyTransformer { 13 | /// The error [this] throws. 14 | static const ERROR = "I am a bad transformer!"; 15 | 16 | /// The asset name that [this] should output. 17 | final String output; 18 | 19 | LazyBadTransformer(this.output); 20 | 21 | bool doIsPrimary(AssetId id) => true; 22 | 23 | void doApply(Transform transform) { 24 | var id = new AssetId.parse(output); 25 | transform.addOutput(new Asset.fromString(id, output)); 26 | } 27 | 28 | void declareOutputs(DeclaringTransform transform) { 29 | var id = new AssetId.parse(output); 30 | transform.declareOutput(id); 31 | throw ERROR; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/package_graph/many_parallel_transformers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.package_graph.transform_test; 6 | 7 | import 'package:barback/src/utils.dart'; 8 | import 'package:scheduled_test/scheduled_test.dart'; 9 | 10 | import '../utils.dart'; 11 | 12 | main() { 13 | initConfig(); 14 | 15 | test("handles many parallel transformers", () { 16 | currentSchedule.timeout *= 3; 17 | var files = new List.generate(100, (i) => "app|$i.txt"); 18 | var rewrite = new RewriteTransformer("txt", "out"); 19 | initGraph(files, { 20 | "app": [ 21 | [rewrite] 22 | ] 23 | }); 24 | 25 | // Pause and resume apply to simulate parallel long-running transformers. 26 | rewrite.pauseApply(); 27 | updateSources(files); 28 | schedule(pumpEventQueue); 29 | rewrite.resumeApply(); 30 | 31 | for (var i = 0; i < 100; i++) { 32 | expectAsset("app|$i.out", "$i.out"); 33 | } 34 | buildShouldSucceed(); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/transformer/has_input.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.has_input; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// Overwrites its primary inputs with descriptions of whether various secondary 14 | /// inputs exist. 15 | class HasInputTransformer extends MockTransformer { 16 | /// The inputs whose existence will be checked. 17 | final List inputs; 18 | 19 | HasInputTransformer(Iterable inputs) 20 | : inputs = inputs.map((input) => new AssetId.parse(input)).toList(); 21 | 22 | bool doIsPrimary(AssetId id) => true; 23 | 24 | Future doApply(Transform transform) { 25 | return Future.wait(inputs.map((input) { 26 | return transform.hasInput(input).then((hasInput) => "$input: $hasInput"); 27 | })).then((results) { 28 | transform.addOutput( 29 | new Asset.fromString(transform.primaryInput.id, results.join(', '))); 30 | }); 31 | } 32 | 33 | String toString() => "has inputs $inputs"; 34 | } 35 | -------------------------------------------------------------------------------- /test/transformer/sync_rewrite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.sync_rewrite; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | /// Like [DeclaringRewriteTransformer], but with no methods returning Futures. 10 | class SyncRewriteTransformer extends Transformer 11 | implements DeclaringTransformer { 12 | final String from; 13 | final String to; 14 | 15 | SyncRewriteTransformer(this.from, this.to); 16 | 17 | bool isPrimary(AssetId id) => id.extension == ".$from"; 18 | 19 | void apply(Transform transform) { 20 | for (var extension in to.split(" ")) { 21 | var id = transform.primaryInput.id.changeExtension(".$extension"); 22 | transform.addOutput(new Asset.fromString(id, "new.$extension")); 23 | } 24 | } 25 | 26 | void declareOutputs(DeclaringTransform transform) { 27 | for (var extension in to.split(" ")) { 28 | var id = transform.primaryId.changeExtension(".$extension"); 29 | transform.declareOutput(id); 30 | } 31 | } 32 | 33 | String toString() => "$from->$to"; 34 | } 35 | -------------------------------------------------------------------------------- /example/markdown_converter/lib/transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:barback/barback.dart'; 6 | import 'package:markdown/markdown.dart'; 7 | 8 | import 'dart:async'; 9 | 10 | class ConvertMarkdown extends Transformer { 11 | // A constructor named "asPlugin" is required. It can be empty, but 12 | // it must be present. It is how pub determines that you want this 13 | // class to be publicly available as a loadable transformer plugin. 14 | ConvertMarkdown.asPlugin(); 15 | 16 | // Any markdown file with one of the following extensions is 17 | // converted to HTML. 18 | String get allowedExtensions => ".md .markdown .mdown"; 19 | 20 | Future apply(Transform transform) async { 21 | var content = await transform.primaryInput.readAsString(); 22 | 23 | // The extension of the output is changed to ".html". 24 | var id = transform.primaryInput.id.changeExtension(".html"); 25 | 26 | var newContent = 27 | "" + markdownToHtml(content) + ""; 28 | transform.addOutput(new Asset.fromString(id, newContent)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/log.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.log; 6 | 7 | import 'package:source_span/source_span.dart'; 8 | 9 | import 'asset/asset_id.dart'; 10 | import 'errors.dart'; 11 | 12 | /// The severity of a logged message. 13 | class LogLevel { 14 | static const INFO = const LogLevel("Info"); 15 | static const FINE = const LogLevel("Fine"); 16 | static const WARNING = const LogLevel("Warning"); 17 | static const ERROR = const LogLevel("Error"); 18 | 19 | final String name; 20 | const LogLevel(this.name); 21 | 22 | String toString() => name; 23 | } 24 | 25 | /// One message logged during a transform. 26 | class LogEntry { 27 | /// The transform that logged the message. 28 | final TransformInfo transform; 29 | 30 | /// The asset that the message is associated with. 31 | final AssetId assetId; 32 | 33 | final LogLevel level; 34 | final String message; 35 | 36 | /// The location that the message pertains to or null if not associated with 37 | /// a [SourceSpan]. 38 | final SourceSpan span; 39 | 40 | LogEntry(this.transform, this.assetId, this.level, this.message, this.span); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/transformer/declaring_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.declaring_transformer; 6 | 7 | import 'dart:async'; 8 | 9 | import 'declaring_transform.dart'; 10 | 11 | /// An interface for [Transformer]s that can cheaply figure out which assets 12 | /// they'll emit without doing the work of actually creating those assets. 13 | /// 14 | /// If a transformer implements this interface, that allows barback to perform 15 | /// optimizations to make the asset graph work more smoothly. 16 | abstract class DeclaringTransformer { 17 | /// Declare which assets would be emitted for the primary input id specified 18 | /// by [transform]. 19 | /// 20 | /// This works a little like [Transformer.apply], with two main differences. 21 | /// First, instead of having access to the primary input's contents, it only 22 | /// has access to its id. Second, instead of emitting [Asset]s, it just emits 23 | /// [AssetId]s through [transform.addOutputId]. 24 | /// 25 | /// If this does asynchronous work, it should return a [Future] that completes 26 | /// once it's finished. 27 | declareOutputs(DeclaringTransform transform); 28 | } 29 | -------------------------------------------------------------------------------- /test/transformer/one_to_many.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.one_to_many; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A [Transformer] that takes an input asset that contains a comma-separated 14 | /// list of paths and outputs a file for each path. 15 | class OneToManyTransformer extends MockTransformer { 16 | final String extension; 17 | 18 | /// Creates a transformer that consumes assets with [extension]. 19 | /// 20 | /// That file contains a comma-separated list of paths and it will output 21 | /// files at each of those paths. 22 | OneToManyTransformer(this.extension); 23 | 24 | bool doIsPrimary(AssetId id) => id.extension == ".$extension"; 25 | 26 | Future doApply(Transform transform) async { 27 | var lines = await (await getPrimary(transform)).readAsString(); 28 | for (var line in lines.split(",")) { 29 | var id = new AssetId(transform.primaryInput.id.package, line); 30 | transform.addOutput(new Asset.fromString(id, "spread $extension")); 31 | } 32 | } 33 | 34 | String toString() => "1->many $extension"; 35 | } 36 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: barback 2 | 3 | # Note! This version number is referenced directly in the pub source code in 4 | # lib/src/barback.dart. Pub implicitly places a version constraint on barback to 5 | # ensure users only select a version of barback that works with their current 6 | # version of pub. 7 | # 8 | # When the minor or patch version of this is upgraded, you *must* update that 9 | # version constraint in pub to stay in sync with this. 10 | version: 0.15.2+16 11 | 12 | author: "Dart Team " 13 | homepage: http://github.com/dart-lang/barback 14 | description: > 15 | A DEPRECATED asset build system for Dart. 16 | 17 | Given a set of input files and a set of transformations (think compilers, 18 | preprocessors and the like), will automatically apply the appropriate 19 | transforms and generate output files. When inputs are modified, automatically 20 | runs the transforms that are affected. 21 | 22 | Runs transforms asynchronously and in parallel when possible to maximize 23 | responsiveness. 24 | dependencies: 25 | async: ">=1.10.0 <3.0.0" 26 | path: ">=0.9.0 <2.0.0" 27 | pool: ">=1.0.0 <2.0.0" 28 | source_span: ">=1.0.0 <2.0.0" 29 | stack_trace: ">=0.9.1 <2.0.0" 30 | collection: "^1.5.0" 31 | dev_dependencies: 32 | scheduled_test: ">=0.9.0 <0.11.0" 33 | unittest: ">=0.9.0 <0.10.0" 34 | environment: 35 | sdk: ">=2.0.0-dev.17.0 <2.0.0" 36 | -------------------------------------------------------------------------------- /test/transformer/aggregate_many_to_many.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.aggregate_many_to_many; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | import 'package:path/path.dart' as path; 11 | 12 | import 'mock_aggregate.dart'; 13 | 14 | /// An [AggregateTransformer] that takes all assets with a given extension, 15 | /// grouped by directory, adds to their contents. 16 | class AggregateManyToManyTransformer extends MockAggregateTransformer { 17 | /// The extension of assets to combine. 18 | final String extension; 19 | 20 | AggregateManyToManyTransformer(this.extension); 21 | 22 | String doClassifyPrimary(AssetId id) { 23 | if (id.extension != ".$extension") return null; 24 | return path.url.dirname(id.path); 25 | } 26 | 27 | Future doApply(AggregateTransform transform) { 28 | return getPrimaryInputs(transform).asyncMap((asset) { 29 | return asset.readAsString().then((contents) { 30 | transform 31 | .addOutput(new Asset.fromString(asset.id, "modified $contents")); 32 | }); 33 | }).toList(); 34 | } 35 | 36 | String toString() => "aggregate $extension->many"; 37 | } 38 | -------------------------------------------------------------------------------- /test/transformer/catch_asset_not_found.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.catch_asset_not_found; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A transformer that tries to load a secondary input and catches an 14 | /// [AssetNotFoundException] if the input doesn't exist. 15 | class CatchAssetNotFoundTransformer extends MockTransformer { 16 | /// The extension of assets this applies to. 17 | final String extension; 18 | 19 | /// The id of the secondary input to load. 20 | final AssetId input; 21 | 22 | CatchAssetNotFoundTransformer(this.extension, String input) 23 | : input = new AssetId.parse(input); 24 | 25 | bool doIsPrimary(AssetId id) => id.extension == extension; 26 | 27 | Future doApply(Transform transform) { 28 | return transform.getInput(input).then((_) { 29 | transform.addOutput( 30 | new Asset.fromString(transform.primaryInput.id, "success")); 31 | }).catchError((e) { 32 | if (e is! AssetNotFoundException) throw e; 33 | transform.addOutput(new Asset.fromString( 34 | transform.primaryInput.id, "failed to load $input")); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/transformer/log.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.log; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that logs given entries during its apply. 12 | class LogTransformer extends MockTransformer { 13 | /// The list of entries that it should log. 14 | /// 15 | /// Each entry has the log level followed by the message, like: 16 | /// 17 | /// error: This is the error message. 18 | final List _entries; 19 | 20 | LogTransformer(this._entries); 21 | 22 | bool doIsPrimary(AssetId id) => true; 23 | 24 | void doApply(Transform transform) { 25 | for (var entry in _entries) { 26 | var parts = entry.split(":"); 27 | var logFn; 28 | switch (parts[0]) { 29 | case "error": 30 | logFn = transform.logger.error; 31 | break; 32 | case "warning": 33 | logFn = transform.logger.warning; 34 | break; 35 | case "info": 36 | logFn = transform.logger.info; 37 | break; 38 | case "fine": 39 | logFn = transform.logger.fine; 40 | break; 41 | } 42 | 43 | logFn(parts[1].trim()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/static_provider_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.static_provider_test; 6 | 7 | import 'package:scheduled_test/scheduled_test.dart'; 8 | 9 | import 'utils.dart'; 10 | 11 | main() { 12 | initConfig(); 13 | test("gets a static source asset", () { 14 | initStaticGraph(["app|foo.txt"], staticPackages: ["app"]); 15 | expectAsset("app|foo.txt"); 16 | buildShouldSucceed(); 17 | }); 18 | 19 | test("doesn't get a nonexistent static source asset", () { 20 | initStaticGraph(["app|foo.txt"], staticPackages: ["app"]); 21 | expectNoAsset("app|bar.txt"); 22 | }); 23 | 24 | test("a transformer can see a static asset", () { 25 | initStaticGraph({ 26 | "static|b.inc": "b", 27 | "app|a.txt": "static|b.inc" 28 | }, staticPackages: [ 29 | "static" 30 | ], transformers: { 31 | "app": [ 32 | [new ManyToOneTransformer("txt")] 33 | ] 34 | }); 35 | updateSources(["app|a.txt"]); 36 | expectAsset("app|a.out", "b"); 37 | }); 38 | 39 | test("can list all static assets", () { 40 | initStaticGraph(["app|foo.txt", "app|bar.txt", "app|baz.txt"], 41 | staticPackages: ["app"]); 42 | expectAllAssets(["app|foo.txt", "app|bar.txt", "app|baz.txt"]); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /lib/barback.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @Deprecated( 6 | 'See https://webdev.dartlang.org/dart-2 for details on how to migrate.') 7 | library barback; 8 | 9 | export 'src/asset/asset.dart'; 10 | export 'src/asset/asset_id.dart'; 11 | export 'src/asset/asset_set.dart'; 12 | export 'src/barback.dart'; 13 | export 'src/build_result.dart'; 14 | export 'src/errors.dart' hide flattenAggregateExceptions; 15 | export 'src/log.dart'; 16 | export 'src/package_provider.dart'; 17 | export 'src/transformer/aggregate_transform.dart'; 18 | export 'src/transformer/aggregate_transformer.dart'; 19 | export 'src/transformer/barback_settings.dart'; 20 | export 'src/transformer/base_transform.dart'; 21 | export 'src/transformer/declaring_aggregate_transform.dart'; 22 | export 'src/transformer/declaring_aggregate_transformer.dart'; 23 | export 'src/transformer/declaring_transform.dart' hide newDeclaringTransform; 24 | export 'src/transformer/declaring_transformer.dart'; 25 | export 'src/transformer/lazy_aggregate_transformer.dart'; 26 | export 'src/transformer/lazy_transformer.dart'; 27 | export 'src/transformer/transform.dart' hide newTransform; 28 | export 'src/transformer/transform_logger.dart'; 29 | export 'src/transformer/transformer.dart'; 30 | export 'src/transformer/transformer_group.dart'; 31 | -------------------------------------------------------------------------------- /test/transformer/rewrite.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.rewrite; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A [Transformer] that takes assets ending with one extension and generates 14 | /// assets with a given extension. 15 | /// 16 | /// Appends the output extension to the contents of the input file. 17 | class RewriteTransformer extends MockTransformer { 18 | final String from; 19 | final String to; 20 | 21 | /// Creates a transformer that rewrites assets whose extension is [from] to 22 | /// one whose extension is [to]. 23 | /// 24 | /// [to] may be a space-separated list in which case multiple outputs will be 25 | /// created for each input. 26 | RewriteTransformer(this.from, this.to); 27 | 28 | bool doIsPrimary(AssetId id) => id.extension == ".$from"; 29 | 30 | Future doApply(Transform transform) { 31 | return getPrimary(transform).then((input) { 32 | return Future.wait(to.split(" ").map((extension) { 33 | var id = input.id.changeExtension(".$extension"); 34 | return input.readAsString().then((content) { 35 | transform.addOutput(new Asset.fromString(id, "$content.$extension")); 36 | }); 37 | })); 38 | }); 39 | } 40 | 41 | String toString() => "$from->$to"; 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/transformer/transformer_group.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.transformer_group; 6 | 7 | /// A [TransformerGroup] encapsulates a phased collection of transformers. 8 | /// 9 | /// A transformer group is defined like a collection of phases, such as you 10 | /// might pass to [Barback.updateTransformers]. Input assets are transformed by 11 | /// the first phase, whose results are passed to the second phase, and so on. 12 | /// 13 | /// However, from the perspective of someone using a [TransformerGroup], it 14 | /// works just like a [Transformer]. It can be included in phases, and will run 15 | /// in parallel to other transformers or groups in those phases. Other 16 | /// transformers and groups will be unable to see any intermediate assets that 17 | /// are generated by one phase of the group and consumed by another. Phases 18 | /// after the one containing the group will be able to see its outputs, though. 19 | class TransformerGroup { 20 | /// The phases that comprise this group. 21 | /// 22 | /// Each element of the inner iterable must be either a [Transformer] or a 23 | /// [TransformerGroup]. 24 | final Iterable phases; 25 | 26 | TransformerGroup(Iterable phases) 27 | : this.phases = phases.map((phase) => phase.toList()).toList(); 28 | 29 | String toString() => "group of $phases"; 30 | } 31 | -------------------------------------------------------------------------------- /test/transformer/check_content_and_rename.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.check_content_and_rename; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A transformer that checks the extension and content of an asset, then 14 | /// produces a new asset with a new extension and new content. 15 | class CheckContentAndRenameTransformer extends MockTransformer { 16 | final String oldExtension; 17 | final String oldContent; 18 | final String newExtension; 19 | final String newContent; 20 | 21 | CheckContentAndRenameTransformer( 22 | {this.oldExtension, 23 | this.oldContent, 24 | this.newExtension, 25 | this.newContent}) { 26 | assert(oldExtension != null); 27 | assert(oldContent != null); 28 | assert(newExtension != null); 29 | assert(newContent != null); 30 | } 31 | 32 | bool doIsPrimary(AssetId id) => id.extension == '.$oldExtension'; 33 | 34 | Future doApply(Transform transform) { 35 | return getPrimary(transform).then((input) { 36 | return input.readAsString().then((contents) { 37 | if (contents != oldContent) return; 38 | 39 | transform.addOutput(new Asset.fromString( 40 | input.id.changeExtension('.$newExtension'), newContent)); 41 | }); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/transformer/declaring_bad.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declaring_bad; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'bad.dart'; 10 | import 'mock.dart'; 11 | 12 | /// A transformer that throws an exception when run, after generating the 13 | /// given outputs. 14 | class DeclaringBadTransformer extends MockTransformer 15 | implements DeclaringTransformer { 16 | /// Whether this should throw an error in [declareOutputs]. 17 | final bool declareError; 18 | 19 | /// Whether this should throw an error in [apply]. 20 | final bool applyError; 21 | 22 | /// The id of the output asset to emit. 23 | final AssetId output; 24 | 25 | DeclaringBadTransformer(String output, 26 | {bool declareError: true, bool applyError: false}) 27 | : this.output = new AssetId.parse(output), 28 | this.declareError = declareError, 29 | this.applyError = applyError; 30 | 31 | bool doIsPrimary(AssetId id) => true; 32 | 33 | void doApply(Transform transform) { 34 | transform.addOutput(new Asset.fromString(output, "bad out")); 35 | if (applyError) throw BadTransformer.ERROR; 36 | } 37 | 38 | void declareOutputs(DeclaringTransform transform) { 39 | if (consumePrimary) transform.consumePrimary(); 40 | transform.declareOutput(output); 41 | if (declareError) throw BadTransformer.ERROR; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013, the Dart project authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /lib/src/asset/asset_node_set.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.asset_node_set; 6 | 7 | import 'package:collection/collection.dart'; 8 | 9 | import 'asset_id.dart'; 10 | import 'asset_node.dart'; 11 | 12 | /// A set of [AssetNode]s that automatically ensures that nodes are removed from 13 | /// the set as soon as they're marked as [AssetState.REMOVED]. 14 | /// 15 | /// Asset nodes may be accessed by their ids. This means that only one node with 16 | /// a given id may be stored in the set at a time. 17 | class AssetNodeSet extends DelegatingSet { 18 | // TODO(nweiz): Use DelegatingMapSet when issue 18705 is fixed. 19 | /// A map from asset ids to assets in the set. 20 | final _assetsById = new Map(); 21 | 22 | AssetNodeSet() : super(new Set()); 23 | 24 | /// Returns the asset node in the set with [id], or `null` if none exists. 25 | AssetNode operator [](AssetId id) => _assetsById[id]; 26 | 27 | bool add(AssetNode node) { 28 | if (node.state.isRemoved) return false; 29 | node.whenRemoved(() { 30 | super.remove(node); 31 | _assetsById.remove(node.id); 32 | }); 33 | _assetsById[node.id] = node; 34 | return super.add(node); 35 | } 36 | 37 | /// Returns whether an asset node with the given [id] is in the set. 38 | bool containsId(AssetId id) => _assetsById.containsKey(id); 39 | 40 | void addAll(Iterable nodes) => nodes.forEach(add); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/transformer/declaring_aggregate_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.declaring_aggregate_transformer; 6 | 7 | import 'declaring_aggregate_transform.dart'; 8 | 9 | /// An interface for [Transformer]s that can cheaply figure out which assets 10 | /// they'll emit without doing the work of actually creating those assets. 11 | /// 12 | /// If a transformer implements this interface, that allows barback to perform 13 | /// optimizations to make the asset graph work more smoothly. 14 | abstract class DeclaringAggregateTransformer { 15 | /// Declare which assets would be emitted for the primary input ids specified 16 | /// by [transform]. 17 | /// 18 | /// This works a little like [AggregateTransformer.apply], with two main 19 | /// differences. First, instead of having access to the primary inputs' 20 | /// contents, it only has access to their ids. Second, instead of emitting 21 | /// [Asset]s, it just emits [AssetId]s through [transform.addOutputId]. 22 | /// 23 | /// If this does asynchronous work, it should return a [Future] that completes 24 | /// once it's finished. 25 | /// 26 | /// This may complete before [DeclaringAggregateTransform.primaryIds] stream 27 | /// is closed. For example, it may know that each key will only have two 28 | /// inputs associated with it, and so use `transform.primaryIds.take(2)` to 29 | /// access only those inputs' ids. 30 | declareOutputs(DeclaringAggregateTransform transform); 31 | } 32 | -------------------------------------------------------------------------------- /test/transformer/declare_assets.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.declare_asset; 6 | 7 | import 'package:barback/barback.dart'; 8 | 9 | import 'mock.dart'; 10 | 11 | /// A transformer that declares some outputs and emits others. 12 | class DeclareAssetsTransformer extends MockTransformer 13 | implements DeclaringTransformer { 14 | /// The assets that the transformer declares that it will emit. 15 | final List declared; 16 | 17 | /// The assets that the transformer actually emits. 18 | /// 19 | /// These assets' contents will be identical to their ids. 20 | final List emitted; 21 | 22 | /// If this is non-`null`, assets are only declared for this input. 23 | final AssetId input; 24 | 25 | DeclareAssetsTransformer(Iterable declared, 26 | {Iterable emitted, String input}) 27 | : this.declared = declared.map((id) => new AssetId.parse(id)).toList(), 28 | this.emitted = (emitted == null ? declared : emitted) 29 | .map((id) => new AssetId.parse(id)) 30 | .toList(), 31 | this.input = input == null ? null : new AssetId.parse(input); 32 | 33 | bool doIsPrimary(AssetId id) => input == null || id == input; 34 | 35 | void doApply(Transform transform) { 36 | for (var id in emitted) { 37 | transform.addOutput(new Asset.fromString(id, id.toString())); 38 | } 39 | } 40 | 41 | void declareOutputs(DeclaringTransform transform) { 42 | declared.forEach(transform.declareOutput); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/utils/cancelable_future.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.utils.cancelable_future; 6 | 7 | import 'dart:async'; 8 | 9 | /// A wrapper for [Future] that can be cancelled. 10 | /// 11 | /// When this is cancelled, that means it won't complete either successfully or 12 | /// with an error, regardless of whether the wrapped Future completes. 13 | /// Cancelling this won't stop whatever code is feeding the wrapped future from 14 | /// running. 15 | class CancelableFuture implements Future { 16 | bool _canceled = false; 17 | final _completer = new Completer.sync(); 18 | 19 | CancelableFuture(Future inner) { 20 | inner.then((result) { 21 | if (_canceled) return; 22 | _completer.complete(result); 23 | }).catchError((error, stackTrace) { 24 | if (_canceled) return; 25 | _completer.completeError(error, stackTrace); 26 | }); 27 | } 28 | 29 | Stream asStream() => _completer.future.asStream(); 30 | Future catchError(Function onError, {bool test(Object error)}) => 31 | _completer.future.catchError(onError, test: test); 32 | Future then(FutureOr onValue(T value), {Function onError}) => 33 | _completer.future.then(onValue, onError: onError); 34 | Future whenComplete(action()) => _completer.future.whenComplete(action); 35 | Future timeout(Duration timeLimit, {void onTimeout()}) => 36 | _completer.future.timeout(timeLimit, onTimeout: onTimeout); 37 | 38 | /// Cancels this future. 39 | void cancel() { 40 | _canceled = true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/asset/asset_forwarder.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.asset_forwarder; 6 | 7 | import 'dart:async'; 8 | 9 | import 'asset_node.dart'; 10 | 11 | /// A wrapper for an [AssetNode] that forwards events to a new node. 12 | /// 13 | /// A forwarder is used when a class wants to forward an [AssetNode] that it 14 | /// gets as an input, but also wants to have control over when that node is 15 | /// marked as removed. The forwarder can be closed, thus removing its output 16 | /// node, without the original node having been removed. 17 | class AssetForwarder { 18 | /// The subscription on the input node. 19 | StreamSubscription _subscription; 20 | 21 | /// The controller for the output node. 22 | final AssetNodeController _controller; 23 | 24 | /// The node to which events are forwarded. 25 | AssetNode get node => _controller.node; 26 | 27 | AssetForwarder(AssetNode node) 28 | : _controller = new AssetNodeController.from(node) { 29 | if (node.state.isRemoved) return; 30 | 31 | _subscription = node.onStateChange.listen((state) { 32 | if (state.isAvailable) { 33 | _controller.setAvailable(node.asset); 34 | } else if (state.isDirty) { 35 | _controller.setDirty(); 36 | } else { 37 | assert(state.isRemoved); 38 | close(); 39 | } 40 | }); 41 | } 42 | 43 | /// Closes the forwarder and marks [node] as removed. 44 | void close() { 45 | if (_controller.node.state.isRemoved) return; 46 | _subscription.cancel(); 47 | _controller.setRemoved(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/lazy_transformer/lib/transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:barback/barback.dart'; 6 | 7 | import 'dart:async'; 8 | 9 | class CodedMessageConverter extends Transformer implements LazyTransformer { 10 | // A constructor named "asPlugin" is required. It can be empty, but 11 | // it must be present. 12 | CodedMessageConverter.asPlugin(); 13 | 14 | Future isPrimary(AssetId id) async => id.extension == '.txt'; 15 | 16 | void declareOutputs(DeclaringTransform transform) { 17 | transform.declareOutput(transform.primaryId.changeExtension('.shhhhh')); 18 | } 19 | 20 | Future apply(Transform transform) async { 21 | var content = await transform.primaryInput.readAsString(); 22 | 23 | // The output file is created with the '.shhhhh' extension. 24 | var id = transform.primaryInput.id.changeExtension('.shhhhh'); 25 | 26 | var newContent = new StringBuffer(); 27 | for (var i = 0; i < content.length; i++) { 28 | newContent.write(rot13(content[i])); 29 | } 30 | transform.addOutput(new Asset.fromString(id, newContent.toString())); 31 | } 32 | 33 | rot13(var ch) { 34 | var c = ch.codeUnitAt(0); 35 | if (c >= 'a'.codeUnitAt(0) && c <= 'm'.codeUnitAt(0)) { 36 | c += 13; 37 | } else if (c >= 'A'.codeUnitAt(0) && c <= 'M'.codeUnitAt(0)) { 38 | c += 13; 39 | } else if (c >= 'n'.codeUnitAt(0) && c <= 'z'.codeUnitAt(0)) { 40 | c -= 13; 41 | } else if (c >= 'N'.codeUnitAt(0) && c <= 'Z'.codeUnitAt(0)) { 42 | c -= 13; 43 | } 44 | return new String.fromCharCode(c); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/asset/asset.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.asset; 6 | 7 | import 'dart:async'; 8 | import 'dart:io'; 9 | import 'dart:convert'; 10 | 11 | import 'asset_id.dart'; 12 | import 'internal_asset.dart'; 13 | 14 | /// A blob of content. 15 | /// 16 | /// Assets may come from the file system, or as the output of a [Transformer]. 17 | /// They are identified by [AssetId]. 18 | /// 19 | /// Custom implementations of [Asset] are not currently supported. 20 | abstract class Asset { 21 | /// The ID for this asset. 22 | final AssetId id; 23 | 24 | factory Asset.fromBytes(AssetId id, List bytes) => 25 | new BinaryAsset(id, bytes); 26 | 27 | factory Asset.fromFile(AssetId id, File file) => new FileAsset(id, file.path); 28 | 29 | factory Asset.fromString(AssetId id, String content) => 30 | new StringAsset(id, content); 31 | 32 | factory Asset.fromPath(AssetId id, String path) => new FileAsset(id, path); 33 | 34 | factory Asset.fromStream(AssetId id, Stream> stream) => 35 | new StreamAsset(id, stream); 36 | 37 | /// Returns the contents of the asset as a string. 38 | /// 39 | /// If the asset was created from a [String] the original string is always 40 | /// returned and [encoding] is ignored. Otherwise, the binary data of the 41 | /// asset is decoded using [encoding], which defaults to [utf8]. 42 | Future readAsString({Encoding encoding}); 43 | 44 | /// Streams the binary contents of the asset. 45 | /// 46 | /// If the asset was created from a [String], this returns its UTF-8 encoding. 47 | Stream> read(); 48 | } 49 | -------------------------------------------------------------------------------- /test/transformer/many_to_one.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.many_to_one; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | 11 | import 'mock.dart'; 12 | 13 | /// A transformer that uses the contents of a file to define the other inputs. 14 | /// 15 | /// Outputs a file with the same name as the primary but with an "out" 16 | /// extension containing the concatenated contents of all non-primary inputs. 17 | class ManyToOneTransformer extends MockTransformer { 18 | final String extension; 19 | 20 | /// Creates a transformer that consumes assets with [extension]. 21 | /// 22 | /// That file contains a comma-separated list of paths and it will input 23 | /// files at each of those paths. 24 | ManyToOneTransformer(this.extension); 25 | 26 | bool doIsPrimary(AssetId id) => id.extension == ".$extension"; 27 | 28 | Future doApply(Transform transform) async { 29 | var primary = await getPrimary(transform); 30 | var contents = await primary.readAsString(); 31 | 32 | // Get all of the included inputs. 33 | var outputs = await Future.wait(contents.split(",").map((path) { 34 | var id; 35 | if (path.contains("|")) { 36 | id = new AssetId.parse(path); 37 | } else { 38 | id = new AssetId(transform.primaryInput.id.package, path); 39 | } 40 | return getInput(transform, id).then((input) => input.readAsString()); 41 | })); 42 | 43 | var id = transform.primaryInput.id.changeExtension(".out"); 44 | transform.addOutput(new Asset.fromString(id, outputs.join())); 45 | } 46 | 47 | String toString() => "many->1 $extension"; 48 | } 49 | -------------------------------------------------------------------------------- /test/cancelable_future_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.cancelable_future_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/src/utils.dart'; 10 | import 'package:barback/src/utils/cancelable_future.dart'; 11 | import 'package:unittest/unittest.dart'; 12 | 13 | import 'utils.dart'; 14 | 15 | main() { 16 | initConfig(); 17 | 18 | var completer; 19 | var future; 20 | setUp(() { 21 | completer = new Completer(); 22 | future = new CancelableFuture(completer.future); 23 | }); 24 | 25 | group("when not canceled", () { 26 | test("correctly completes successfully", () { 27 | expect(future, completion(equals("success"))); 28 | completer.complete("success"); 29 | }); 30 | 31 | test("correctly completes with an error", () { 32 | expect(future, throwsA(equals("error"))); 33 | completer.completeError("error"); 34 | }); 35 | }); 36 | 37 | group("when canceled", () { 38 | test("never completes successfully", () { 39 | var completed = false; 40 | future.whenComplete(() { 41 | completed = true; 42 | }); 43 | 44 | future.cancel(); 45 | completer.complete("success"); 46 | 47 | expect(pumpEventQueue().then((_) => completed), completion(isFalse)); 48 | }); 49 | 50 | test("never completes with an error", () { 51 | var completed = false; 52 | future.catchError((_) {}).whenComplete(() { 53 | completed = true; 54 | }); 55 | 56 | future.cancel(); 57 | completer.completeError("error"); 58 | 59 | expect(pumpEventQueue().then((_) => completed), completion(isFalse)); 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /test/transformer/aggregate_many_to_one.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer.aggregate_many_to_one; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | import 'package:path/path.dart' as path; 11 | 12 | import 'mock_aggregate.dart'; 13 | 14 | /// An [AggregateTransformer] that applies to all assets with a given extension. 15 | /// For each directory containing any of these assets, it produces an output 16 | /// file that contains the concatenation of all matched assets in that 17 | /// directory in alphabetic order by name. 18 | class AggregateManyToOneTransformer extends MockAggregateTransformer { 19 | /// The extension of assets to combine. 20 | final String extension; 21 | 22 | /// The basename of the output asset. 23 | /// 24 | /// The output asset's path will contain the directory name of the inputs as 25 | /// well. 26 | final String output; 27 | 28 | AggregateManyToOneTransformer(this.extension, this.output); 29 | 30 | String doClassifyPrimary(AssetId id) { 31 | if (id.extension != ".$extension") return null; 32 | return path.url.dirname(id.path); 33 | } 34 | 35 | Future doApply(AggregateTransform transform) async { 36 | var assets = await getPrimaryInputs(transform).toList(); 37 | assets.sort((asset1, asset2) => asset1.id.path.compareTo(asset2.id.path)); 38 | var contents = 39 | await Future.wait(assets.map((asset) => asset.readAsString())); 40 | var id = 41 | new AssetId(transform.package, path.url.join(transform.key, output)); 42 | transform.addOutput(new Asset.fromString(id, contents.join('\n'))); 43 | } 44 | 45 | String toString() => "aggregate $extension->$output"; 46 | } 47 | -------------------------------------------------------------------------------- /example/aggregate_transformer/lib/transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:barback/barback.dart'; 6 | import 'package:path/path.dart' as p; 7 | 8 | import 'dart:async'; 9 | 10 | class MakeBook extends AggregateTransformer { 11 | // All transformers need to implement "asPlugin" to let Pub know that they 12 | // are transformers. 13 | MakeBook.asPlugin(); 14 | 15 | // Implement the classifyPrimary method to claim any assets that you want 16 | // to handle. Return a value for the assets you want to handle, 17 | // or null for those that you do not want to handle. 18 | classifyPrimary(AssetId id) { 19 | // Only process assets where the filename ends with "recipe.html". 20 | if (!id.path.endsWith('recipe.html')) return null; 21 | 22 | // Return the path string, minus the recipe itself. 23 | // This is where the output asset will be written. 24 | return p.url.dirname(id.path); 25 | } 26 | 27 | // Implement the apply method to process the assets and create the 28 | // output asset. 29 | Future apply(AggregateTransform transform) async { 30 | var buffer = new StringBuffer()..write(''); 31 | 32 | var assets = await transform.primaryInputs.toList(); 33 | assets.sort((x, y) => x.id.compareTo(y.id)); 34 | for (var asset in assets) { 35 | var content = await asset.readAsString(); 36 | buffer.write(content); 37 | buffer.write('
'); 38 | } 39 | buffer.write(''); 40 | // Write the output back to the same directory, 41 | // in a file named recipes.html. 42 | var id = new AssetId( 43 | transform.package, p.url.join(transform.key, "recipes.html")); 44 | transform.addOutput(new Asset.fromString(id, buffer.toString())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/too_many_open_files_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.too_many_open_files_test; 6 | 7 | import 'dart:async'; 8 | import 'dart:io'; 9 | 10 | import 'package:barback/barback.dart'; 11 | import 'package:path/path.dart' as pathos; 12 | import 'package:unittest/unittest.dart'; 13 | 14 | import 'utils.dart'; 15 | 16 | main() { 17 | initConfig(); 18 | 19 | test("handles many simultaneous asset read() calls", () { 20 | runOnManyFiles((asset) => asset.read().toList()); 21 | }); 22 | 23 | test("handles many simultaneous asset readToString() calls", () { 24 | runOnManyFiles((asset) => asset.readAsString()); 25 | }); 26 | } 27 | 28 | runOnManyFiles(Future assetHandler(Asset asset)) { 29 | // Make a text file in a temp directory. 30 | var tempDir = Directory.systemTemp.createTempSync("barback").path; 31 | var filePath = pathos.join(tempDir, "out.txt"); 32 | 33 | // Make sure it's large enough to not be read in a single chunk. 34 | var contents = new StringBuffer(); 35 | for (var i = 0; i < 1024; i++) { 36 | contents.write( 37 | "this is a sixty four character long string that describes itself"); 38 | } 39 | 40 | new File(filePath).writeAsStringSync(contents.toString()); 41 | 42 | var id = new AssetId("myapp", "out.txt"); 43 | 44 | // Create a large number of assets, larger than the file descriptor limit 45 | // of most machines and start reading from all of them. 46 | var futures = []; 47 | for (var i = 0; i < 1000; i++) { 48 | var asset = new Asset.fromPath(id, filePath); 49 | futures.add(assetHandler(asset)); 50 | } 51 | 52 | expect( 53 | Future.wait(futures).whenComplete(() { 54 | new Directory(tempDir).delete(recursive: true); 55 | }), 56 | completes); 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/package_provider.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.package_provider; 6 | 7 | import 'dart:async'; 8 | 9 | import 'asset/asset.dart'; 10 | import 'asset/asset_id.dart'; 11 | 12 | /// API for locating and accessing packages on disk. 13 | /// 14 | /// Implemented by pub and provided to barback so that it isn't coupled 15 | /// directly to pub. 16 | abstract class PackageProvider { 17 | /// The names of all packages that can be provided by this provider. 18 | /// 19 | /// This is equal to the transitive closure of the entrypoint package 20 | /// dependencies. 21 | Iterable get packages; 22 | 23 | /// Loads an asset from disk. 24 | /// 25 | /// This should be re-entrant; it may be called multiple times with the same 26 | /// id before the previously returned future has completed. 27 | /// 28 | /// If no asset with [id] exists, the provider should throw an 29 | /// [AssetNotFoundException]. 30 | Future getAsset(AssetId id); 31 | } 32 | 33 | /// A PackageProvider for which some packages are known to be static—that is, 34 | /// the package has no transformers and its assets won't ever change. 35 | /// 36 | /// For static packages, rather than telling barback up-front which assets that 37 | /// package contains via [Barback.updateSources], barback will lazily query the 38 | /// provider for an asset when it's needed. This is much more efficient. 39 | abstract class StaticPackageProvider implements PackageProvider { 40 | /// The names of all static packages provided by this provider. 41 | /// 42 | /// This must be disjoint from [packages]. 43 | Iterable get staticPackages; 44 | 45 | /// Returns all ids of assets in [package]. 46 | /// 47 | /// This is used for [Barback.getAllAssets]. 48 | Stream getAllAssetIds(String package); 49 | } 50 | -------------------------------------------------------------------------------- /test/logger_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.logger_test; 6 | 7 | import 'package:barback/barback.dart'; 8 | import 'package:scheduled_test/scheduled_test.dart'; 9 | 10 | import 'utils.dart'; 11 | import 'transformer/log.dart'; 12 | 13 | main() { 14 | initConfig(); 15 | 16 | test("logs messages from a transformer", () { 17 | var transformer = new LogTransformer([ 18 | "error: This is an error.", 19 | "warning: This is a warning.", 20 | "info: This is info.", 21 | "fine: This is fine." 22 | ]); 23 | initGraph([ 24 | "app|foo.txt" 25 | ], { 26 | "app": [ 27 | [transformer] 28 | ] 29 | }); 30 | 31 | updateSources(["app|foo.txt"]); 32 | buildShouldLog(LogLevel.ERROR, equals("This is an error.")); 33 | buildShouldLog(LogLevel.WARNING, equals("This is a warning.")); 34 | buildShouldLog(LogLevel.INFO, equals("This is info.")); 35 | buildShouldLog(LogLevel.FINE, equals("This is fine.")); 36 | }); 37 | 38 | test("logs messages from a transformer group", () { 39 | var transformer = new LogTransformer([ 40 | "error: This is an error.", 41 | "warning: This is a warning.", 42 | "info: This is info.", 43 | "fine: This is fine." 44 | ]); 45 | 46 | initGraph([ 47 | "app|foo.txt" 48 | ], { 49 | "app": [ 50 | [ 51 | new TransformerGroup([ 52 | [transformer] 53 | ]) 54 | ] 55 | ] 56 | }); 57 | 58 | updateSources(["app|foo.txt"]); 59 | buildShouldLog(LogLevel.ERROR, equals("This is an error.")); 60 | buildShouldLog(LogLevel.WARNING, equals("This is a warning.")); 61 | buildShouldLog(LogLevel.INFO, equals("This is info.")); 62 | buildShouldLog(LogLevel.FINE, equals("This is fine.")); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/utils/multiset.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.utils.multiset; 6 | 7 | import 'dart:collection'; 8 | 9 | /// A set of objects where each object can appear multiple times. 10 | /// 11 | /// Like a set, this has amortized O(1) insertion, removal, and 12 | /// existence-checking of elements. Counting the number of copies of an element 13 | /// in the set is also amortized O(1). 14 | /// 15 | /// Distinct elements retain insertion order. Additional copies of an element 16 | /// beyond the first are grouped with the original element. 17 | /// 18 | /// If multiple equal elements are added, only the first actual object is 19 | /// retained. 20 | class Multiset extends IterableBase { 21 | /// A map from each element in the set to the number of copies of that element 22 | /// in the set. 23 | final _map = new Map(); 24 | 25 | Iterator get iterator { 26 | return _map.keys 27 | .expand((element) => 28 | new Iterable.generate(_map[element], (_) => element)) 29 | .iterator; 30 | } 31 | 32 | Multiset() : super(); 33 | 34 | /// Creates a multi-set and initializes it using the contents of [other]. 35 | Multiset.from(Iterable other) : super() { 36 | other.forEach(add); 37 | } 38 | 39 | /// Adds [value] to the set. 40 | void add(E value) { 41 | _map.putIfAbsent(value, () => 0); 42 | _map[value] += 1; 43 | } 44 | 45 | /// Removes one copy of [value] from the set. 46 | /// 47 | /// Returns whether a copy of [value] was removed, regardless of whether more 48 | /// copies remain. 49 | bool remove(E value) { 50 | if (!_map.containsKey(value)) return false; 51 | 52 | _map[value] -= 1; 53 | if (_map[value] == 0) _map.remove(value); 54 | return true; 55 | } 56 | 57 | /// Returns whether [value] is in the set. 58 | bool contains(Object value) => _map.containsKey(value); 59 | 60 | /// Returns the number of copies of [value] in the set. 61 | int count(E value) => _map.containsKey(value) ? _map[value] : 0; 62 | } 63 | -------------------------------------------------------------------------------- /test/transformer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.transformer_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | import 'package:unittest/unittest.dart'; 11 | 12 | import 'utils.dart'; 13 | 14 | main() { 15 | initConfig(); 16 | 17 | group("isPrimary", () { 18 | test("defaults to allowedExtensions", () { 19 | var transformer = new ExtensionTransformer(".txt .bin"); 20 | expect(transformer.isPrimary(new AssetId("pkg", "foo.txt")), isTrue); 21 | 22 | expect(transformer.isPrimary(new AssetId("pkg", "foo.bin")), isTrue); 23 | 24 | expect(transformer.isPrimary(new AssetId("pkg", "foo.nottxt")), isFalse); 25 | }); 26 | 27 | test("supports multi-level extensions with allowedExtensions", () { 28 | var transformer = new ExtensionTransformer(".dart.js"); 29 | expect(transformer.isPrimary(new AssetId("pkg", "foo.dart.js")), isTrue); 30 | 31 | expect(transformer.isPrimary(new AssetId("pkg", "foo.js")), isFalse); 32 | 33 | expect(transformer.isPrimary(new AssetId("pkg", "foo.dart")), isFalse); 34 | }); 35 | 36 | test("throws an error for extensions without periods", () { 37 | expect(() => new ExtensionTransformer("dart"), throwsFormatException); 38 | }); 39 | 40 | test("allows all files if allowedExtensions is not overridden", () { 41 | var transformer = new MockTransformer(); 42 | expect(transformer.isPrimary(new AssetId("pkg", "foo.txt")), isTrue); 43 | 44 | expect(transformer.isPrimary(new AssetId("pkg", "foo.bin")), isTrue); 45 | 46 | expect(transformer.isPrimary(new AssetId("pkg", "anything")), isTrue); 47 | }); 48 | }); 49 | } 50 | 51 | class MockTransformer extends Transformer { 52 | MockTransformer(); 53 | 54 | Future apply(Transform transform) => new Future.value(); 55 | } 56 | 57 | class ExtensionTransformer extends Transformer { 58 | final String allowedExtensions; 59 | 60 | ExtensionTransformer(this.allowedExtensions); 61 | 62 | Future apply(Transform transform) => new Future.value(); 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/transformer/barback_settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.barback_settings; 6 | 7 | /// A generic settings object for providing configuration details to 8 | /// [Transformer]s. 9 | /// 10 | /// Barback does not specify *how* this is provided to transformers. It is up 11 | /// to a host application to handle this. (For example, pub passes this to the 12 | /// transformer's constructor.) 13 | class BarbackSettings { 14 | /// An open-ended map of configuration properties specific to this 15 | /// transformer. 16 | /// 17 | /// The contents of the map should be serializable across isolates, but 18 | /// otherwise can contain whatever you want. 19 | final Map configuration; 20 | 21 | /// The mode that user is running Barback in. 22 | /// 23 | /// This will be the same for all transformers in a running instance of 24 | /// Barback. 25 | final BarbackMode mode; 26 | 27 | BarbackSettings(this.configuration, this.mode); 28 | } 29 | 30 | /// Enum-like class for specifying a mode that transformers may be run in. 31 | /// 32 | /// Note that this is not a *closed* set of enum values. Host applications may 33 | /// define their own values for this, so a transformer relying on it should 34 | /// ensure that it behaves sanely with unknown values. 35 | class BarbackMode { 36 | /// The normal mode used during development. 37 | static const DEBUG = const BarbackMode._("debug"); 38 | 39 | /// The normal mode used to build an application for deploying to production. 40 | static const RELEASE = const BarbackMode._("release"); 41 | 42 | /// The name of the mode. 43 | /// 44 | /// By convention, this is a lowercase string. 45 | final String name; 46 | 47 | /// Create a mode named [name]. 48 | factory BarbackMode(String name) { 49 | // Use canonical instances of known names. 50 | switch (name) { 51 | case "debug": 52 | return BarbackMode.DEBUG; 53 | case "release": 54 | return BarbackMode.RELEASE; 55 | default: 56 | return new BarbackMode._(name); 57 | } 58 | } 59 | 60 | const BarbackMode._(this.name); 61 | 62 | String toString() => name; 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/asset/asset_set.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.asset_set; 6 | 7 | import 'dart:collection'; 8 | 9 | import 'asset.dart'; 10 | import 'asset_id.dart'; 11 | 12 | /// A set of [Asset]s with distinct IDs. 13 | /// 14 | /// This uses the [AssetId] of each asset to determine uniqueness, so no two 15 | /// assets with the same ID can be in the set. 16 | class AssetSet extends IterableBase { 17 | final _assets = new Map(); 18 | 19 | /// The ids of the assets in the set. 20 | Iterable get ids => _assets.keys; 21 | 22 | AssetSet(); 23 | 24 | /// Creates a new AssetSet from the contents of [other]. 25 | /// 26 | /// If multiple assets in [other] have the same id, the last one takes 27 | /// precedence. 28 | AssetSet.from(Iterable other) { 29 | for (var asset in other) { 30 | _assets[asset.id] = asset; 31 | } 32 | } 33 | 34 | Iterator get iterator => _assets.values.iterator; 35 | 36 | int get length => _assets.length; 37 | 38 | /// Gets the [Asset] in the set with [id], or returns `null` if no asset with 39 | /// that ID is present. 40 | Asset operator [](AssetId id) => _assets[id]; 41 | 42 | /// Adds [asset] to the set. 43 | /// 44 | /// If there is already an asset with that ID in the set, it is replaced by 45 | /// the new one. Returns [asset]. 46 | Asset add(Asset asset) { 47 | _assets[asset.id] = asset; 48 | return asset; 49 | } 50 | 51 | /// Adds [assets] to the set. 52 | void addAll(Iterable assets) { 53 | assets.forEach(add); 54 | } 55 | 56 | /// Returns `true` if the set contains [asset]. 57 | bool contains(Object asset) => asset is Asset && _assets[asset.id] == asset; 58 | 59 | /// Returns `true` if the set contains an [Asset] with [id]. 60 | bool containsId(AssetId id) { 61 | return _assets.containsKey(id); 62 | } 63 | 64 | /// If the set contains an [Asset] with [id], removes and returns it. 65 | Asset removeId(AssetId id) => _assets.remove(id); 66 | 67 | /// Removes all assets from the set. 68 | void clear() { 69 | _assets.clear(); 70 | } 71 | 72 | String toString() => _assets.toString(); 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/graph/node_status.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.node_status; 6 | 7 | /// The status of a node in barback's package graph. 8 | /// 9 | /// A node has three possible statuses: [IDLE], [MATERIALIZING], and [RUNNING]. 10 | /// These are ordered from least dirty to most dirty; the [dirtier] and 11 | /// [dirtiest] functions make use of this ordering. 12 | class NodeStatus { 13 | /// The node has finished its work and won't do anything else until external 14 | /// input causes it to. 15 | /// 16 | /// For deferred nodes, this may indicate that they're finished declaring 17 | /// their outputs and waiting to be forced. 18 | static const IDLE = const NodeStatus("idle"); 19 | 20 | /// The node has declared its outputs but their concrete values are still 21 | /// being generated. 22 | /// 23 | /// This is only meaningful for nodes that are or contain declaring 24 | /// transformers. Note that a lazy transformer that's declared its outputs but 25 | /// isn't actively working to generate them is considered [IDLE], not 26 | /// [MATERIALIZING]. 27 | static const MATERIALIZING = const NodeStatus("materializing"); 28 | 29 | /// The node is actively working on declaring or generating its outputs. 30 | /// 31 | /// Declaring transformers are only considered dirty until they're finished 32 | /// declaring their outputs; past that point, they're always either 33 | /// [MATERIALIZING] or [IDLE]. Non-declaring transformers, by contrast, are 34 | /// always either [RUNNING] or [IDLE]. 35 | static const RUNNING = const NodeStatus("running"); 36 | 37 | final String _name; 38 | 39 | /// Returns the dirtiest status in [statuses]. 40 | static NodeStatus dirtiest(Iterable statuses) => statuses.fold( 41 | NodeStatus.IDLE, (status1, status2) => status1.dirtier(status2)); 42 | 43 | const NodeStatus(this._name); 44 | 45 | String toString() => _name; 46 | 47 | /// Returns [this] or [other], whichever is dirtier. 48 | NodeStatus dirtier(NodeStatus other) { 49 | if (this == RUNNING || other == RUNNING) return RUNNING; 50 | if (this == MATERIALIZING || other == MATERIALIZING) return MATERIALIZING; 51 | return IDLE; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/asset_id_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.asset_id_test; 6 | 7 | import 'package:barback/barback.dart'; 8 | import 'package:unittest/unittest.dart'; 9 | 10 | import 'utils.dart'; 11 | 12 | main() { 13 | initConfig(); 14 | group("constructor", () { 15 | test("normalizes the path", () { 16 | var id = new AssetId("app", r"path/././/to/drop/..//asset.txt"); 17 | expect(id.path, equals("path/to/asset.txt")); 18 | }); 19 | 20 | test("normalizes backslashes to slashes in the path", () { 21 | var id = new AssetId("app", r"path\to/asset.txt"); 22 | expect(id.path, equals("path/to/asset.txt")); 23 | }); 24 | }); 25 | 26 | group("parse", () { 27 | test("parses the package and path", () { 28 | var id = new AssetId.parse("package|path/to/asset.txt"); 29 | expect(id.package, equals("package")); 30 | expect(id.path, equals("path/to/asset.txt")); 31 | }); 32 | 33 | test("throws if there are multiple '|'", () { 34 | expect(() => new AssetId.parse("app|path|wtf"), throwsFormatException); 35 | }); 36 | 37 | test("throws if the package name is empty '|'", () { 38 | expect(() => new AssetId.parse("|asset.txt"), throwsFormatException); 39 | }); 40 | 41 | test("throws if the path is empty '|'", () { 42 | expect(() => new AssetId.parse("app|"), throwsFormatException); 43 | }); 44 | 45 | test("normalizes the path", () { 46 | var id = new AssetId.parse(r"app|path/././/to/drop/..//asset.txt"); 47 | expect(id.path, equals("path/to/asset.txt")); 48 | }); 49 | 50 | test("normalizes backslashes to slashes in the path", () { 51 | var id = new AssetId.parse(r"app|path\to/asset.txt"); 52 | expect(id.path, equals("path/to/asset.txt")); 53 | }); 54 | }); 55 | 56 | test("equals another ID with the same package and path", () { 57 | expect(new AssetId.parse("foo|asset.txt"), 58 | equals(new AssetId.parse("foo|asset.txt"))); 59 | 60 | expect(new AssetId.parse("foo|asset.txt"), 61 | isNot(equals(new AssetId.parse("bar|asset.txt")))); 62 | 63 | expect(new AssetId.parse("foo|asset.txt"), 64 | isNot(equals(new AssetId.parse("bar|other.txt")))); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/build_result.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.build_result; 6 | 7 | import 'errors.dart'; 8 | import 'utils.dart'; 9 | 10 | /// An event indicating that the cascade has finished building all assets. 11 | /// 12 | /// A build can end either in success or failure. If there were no errors during 13 | /// the build, it's considered to be a success; any errors render it a failure, 14 | /// although individual assets may still have built successfully. 15 | class BuildResult { 16 | // TODO(rnystrom): Revise how to track error results. Errors can come from 17 | // both logs and exceptions. Accumulating them is likely slow and a waste of 18 | // memory. If we do want to accumulate them, we should at least unify them 19 | // in a single collection (probably of log entries). 20 | /// All errors that were thrown during the build. 21 | final Set errors; 22 | 23 | /// `true` if the build succeeded. 24 | bool get succeeded => errors.isEmpty; 25 | 26 | BuildResult(Iterable errors) 27 | : errors = flattenAggregateExceptions(errors).toSet(); 28 | 29 | /// Creates a build result indicating a successful build. 30 | /// 31 | /// This equivalent to a build result with no errors. 32 | BuildResult.success() : this([]); 33 | 34 | /// Creates a single [BuildResult] that contains all of the errors of 35 | /// [results]. 36 | factory BuildResult.aggregate(Iterable results) { 37 | var errors = unionAll(results.map((result) => result.errors)); 38 | return new BuildResult(errors); 39 | } 40 | 41 | String toString() { 42 | if (succeeded) return "success"; 43 | 44 | return "errors:\n" + 45 | errors.map((error) { 46 | var stackTrace = null; 47 | if (error is TransformerException) { 48 | stackTrace = error.stackTrace.terse; 49 | } else if (error is AssetLoadException) { 50 | stackTrace = error.stackTrace.terse; 51 | } 52 | 53 | var msg = new StringBuffer(); 54 | msg.write(prefixLines(error.toString())); 55 | if (stackTrace != null) { 56 | msg.write("\n\n"); 57 | msg.write("Stack chain:\n"); 58 | msg.write(prefixLines(stackTrace.toString())); 59 | } 60 | return msg.toString(); 61 | }).join("\n\n"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/utils/file_pool.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.utils.file_pool; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | import 'dart:io'; 10 | 11 | import 'package:pool/pool.dart'; 12 | 13 | import '../utils.dart'; 14 | 15 | /// Manages a pool of files that are opened for reading to cope with maximum 16 | /// file descriptor limits. 17 | /// 18 | /// If a file cannot be opened because too many files are already open, this 19 | /// will defer the open until a previously opened file is closed and then try 20 | /// again. If this doesn't succeed after a certain amount of time, the open 21 | /// will fail and the original "too many files" exception will be thrown. 22 | class FilePool { 23 | /// The underlying pool. 24 | /// 25 | /// The maximum number of allocated descriptors is based on empirical tests 26 | /// that indicate that beyond 32, additional file reads don't provide 27 | /// substantial additional throughput. 28 | final Pool _pool = new Pool(32, timeout: new Duration(seconds: 60)); 29 | 30 | /// Opens the file at [path] for reading. 31 | /// 32 | /// When the returned stream is listened to, if there are too many files 33 | /// open, this will wait for a previously opened file to be closed and then 34 | /// try again. 35 | Stream> openRead(String path) { 36 | return futureStream(_pool.request().then((resource) { 37 | return new File(path) 38 | .openRead() 39 | .transform(new StreamTransformer.fromHandlers(handleDone: (sink) { 40 | sink.close(); 41 | resource.release(); 42 | })); 43 | })); 44 | } 45 | 46 | /// Reads [path] as a string using [encoding]. 47 | /// 48 | /// If there are too many files open and the read fails, this will wait for 49 | /// a previously opened file to be closed and then try again. 50 | Future readAsString(String path, Encoding encoding) { 51 | return _readAsBytes(path).then(encoding.decode); 52 | } 53 | 54 | /// Reads [path] as a list of bytes, using [openRead] to retry if there are 55 | /// failures. 56 | Future> _readAsBytes(String path) { 57 | var completer = new Completer>(); 58 | var builder = new BytesBuilder(); 59 | 60 | openRead(path).listen(builder.add, onDone: () { 61 | completer.complete(builder.takeBytes()); 62 | }, onError: completer.completeError, cancelOnError: true); 63 | 64 | return completer.future; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/graph/node_streams.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.node_streams; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_node.dart'; 10 | import '../log.dart'; 11 | import '../utils/stream_pool.dart'; 12 | import 'node_status.dart'; 13 | 14 | /// A collection of streams that are common to nodes in barback's package graph. 15 | class NodeStreams { 16 | /// A stream that emits an event every time the node's status changes. 17 | /// 18 | /// This will emit the new status. It's guaranteed to emit an event only when 19 | /// the status changes from the previous value. To ensure this, callers should 20 | /// emit status changes using [changeStatus]. The initial status is assumed to 21 | /// be [NodeStatus.RUNNING]. 22 | Stream get onStatusChange => _onStatusChangeController.stream; 23 | final _onStatusChangeController = 24 | new StreamController.broadcast(sync: true); 25 | 26 | /// A stream that emits any new assets produced by the node. 27 | /// 28 | /// Assets are emitted synchronously to ensure that any changes are thoroughly 29 | /// propagated as soon as they occur. 30 | Stream get onAsset => onAssetPool.stream; 31 | final onAssetPool = new StreamPool.broadcast(); 32 | final onAssetController = 33 | new StreamController.broadcast(sync: true); 34 | 35 | /// A stream that emits an event whenever any the node logs an entry. 36 | Stream get onLog => onLogPool.stream; 37 | final onLogPool = new StreamPool.broadcast(); 38 | final onLogController = new StreamController.broadcast(sync: true); 39 | 40 | var _previousStatus = NodeStatus.RUNNING; 41 | 42 | /// Whether [this] has been closed. 43 | bool get isClosed => onAssetController.isClosed; 44 | 45 | NodeStreams() { 46 | onAssetPool.add(onAssetController.stream); 47 | onLogPool.add(onLogController.stream); 48 | } 49 | 50 | /// Emits a status change notification via [onStatusChange]. 51 | /// 52 | /// This guarantees that a change notification won't be emitted if the status 53 | /// didn't actually change. 54 | void changeStatus(NodeStatus status) { 55 | if (_previousStatus != status) _onStatusChangeController.add(status); 56 | } 57 | 58 | /// Closes all the streams. 59 | void close() { 60 | _onStatusChangeController.close(); 61 | onAssetController.close(); 62 | onAssetPool.close(); 63 | onLogController.close(); 64 | onLogPool.close(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/utils/stream_replayer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.utils.stream_replayer; 6 | 7 | import 'dart:async'; 8 | import 'dart:collection'; 9 | 10 | import '../utils.dart'; 11 | 12 | /// Records the values and errors that are sent through a stream and allows them 13 | /// to be replayed arbitrarily many times. 14 | /// 15 | /// This only listens to the wrapped stream when a replayed stream gets a 16 | /// listener. 17 | class StreamReplayer { 18 | /// The wrapped stream. 19 | final Stream _stream; 20 | 21 | /// Whether or not [this] has started listening to [_stream]. 22 | bool _isSubscribed = false; 23 | 24 | /// Whether or not [_stream] has been closed. 25 | bool _isClosed = false; 26 | 27 | /// The buffer of events or errors that have already been emitted by 28 | /// [_stream]. 29 | /// 30 | /// Each element is a [Fallible] that's either a value or an error sent 31 | /// through the stream. 32 | final _buffer = new Queue>(); 33 | 34 | /// The controllers that are listening for future events from [_stream]. 35 | final _controllers = new Set>(); 36 | 37 | StreamReplayer(this._stream); 38 | 39 | /// Returns a stream that replays the values and errors of the input stream. 40 | /// 41 | /// This stream is a buffered stream. 42 | Stream getReplay() { 43 | var controller = new StreamController(onListen: _subscribe); 44 | 45 | for (var eventOrError in _buffer) { 46 | if (eventOrError.hasValue) { 47 | controller.add(eventOrError.value); 48 | } else { 49 | controller.addError(eventOrError.error, eventOrError.stackTrace); 50 | } 51 | } 52 | if (_isClosed) { 53 | controller.close(); 54 | } else { 55 | _controllers.add(controller); 56 | } 57 | return controller.stream; 58 | } 59 | 60 | /// Subscribe to [_stream] if we haven't yet done so. 61 | void _subscribe() { 62 | if (_isSubscribed || _isClosed) return; 63 | _isSubscribed = true; 64 | 65 | _stream.listen((data) { 66 | _buffer.add(new Fallible.withValue(data)); 67 | for (var controller in _controllers) { 68 | controller.add(data); 69 | } 70 | }, onError: (error, [stackTrace]) { 71 | _buffer.add(new Fallible.withError(error, stackTrace)); 72 | for (var controller in _controllers) { 73 | controller.addError(error, stackTrace); 74 | } 75 | }, onDone: () { 76 | _isClosed = true; 77 | for (var controller in _controllers) { 78 | controller.close(); 79 | } 80 | _controllers.clear(); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/utils/stream_pool.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.utils.stream_pool; 6 | 7 | import 'dart:async'; 8 | 9 | /// A pool of streams whose events are unified and emitted through a central 10 | /// stream. 11 | class StreamPool { 12 | /// The stream through which all events from streams in the pool are emitted. 13 | Stream get stream => _controller.stream; 14 | final StreamController _controller; 15 | 16 | /// Subscriptions to the streams that make up the pool. 17 | final _subscriptions = new Map, StreamSubscription>(); 18 | 19 | /// Creates a new stream pool that only supports a single subscriber. 20 | /// 21 | /// Any events from broadcast streams in the pool will be buffered until a 22 | /// listener is subscribed. 23 | StreamPool() 24 | // Create the controller as sync so that any sync input streams will be 25 | // forwarded synchronously. Async input streams will have their asynchrony 26 | // preserved, since _controller.add will be called asynchronously. 27 | : _controller = new StreamController(sync: true); 28 | 29 | /// Creates a new stream pool where [stream] can be listened to more than 30 | /// once. 31 | /// 32 | /// Any events from buffered streams in the pool will be emitted immediately, 33 | /// regardless of whether [stream] has any subscribers. 34 | StreamPool.broadcast() 35 | // Create the controller as sync so that any sync input streams will be 36 | // forwarded synchronously. Async input streams will have their asynchrony 37 | // preserved, since _controller.add will be called asynchronously. 38 | : _controller = new StreamController.broadcast(sync: true); 39 | 40 | /// Adds [stream] as a member of this pool. 41 | /// 42 | /// Any events from [stream] will be emitted through [this.stream]. If 43 | /// [stream] is sync, they'll be emitted synchronously; if [stream] is async, 44 | /// they'll be emitted asynchronously. 45 | void add(Stream stream) { 46 | if (_subscriptions.containsKey(stream)) return; 47 | _subscriptions[stream] = stream.listen(_controller.add, 48 | onError: _controller.addError, onDone: () => remove(stream)); 49 | } 50 | 51 | /// Removes [stream] as a member of this pool. 52 | void remove(Stream stream) { 53 | var subscription = _subscriptions.remove(stream); 54 | if (subscription != null) subscription.cancel(); 55 | } 56 | 57 | /// Removes all streams from this pool and closes [stream]. 58 | void close() { 59 | for (var subscription in _subscriptions.values) { 60 | subscription.cancel(); 61 | } 62 | _subscriptions.clear(); 63 | _controller.close(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/package_graph/repetition_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.package_graph.transform_test; 6 | 7 | import 'package:scheduled_test/scheduled_test.dart'; 8 | 9 | import '../utils.dart'; 10 | 11 | // This tests the behavior of barback under many operations happening in quick 12 | // succession. Since Barback is so asynchronous, it's easy for it to have subtle 13 | // dependencies on the commonly-used and -tested usage patterns. These tests 14 | // exist to stress-test less-common usage patterns in order to root out 15 | // additional bugs. 16 | 17 | main() { 18 | initConfig(); 19 | 20 | test("updates sources many times", () { 21 | initGraph([ 22 | "app|foo.txt" 23 | ], { 24 | "app": [ 25 | [new RewriteTransformer("txt", "out")] 26 | ] 27 | }); 28 | 29 | for (var i = 0; i < 1000; i++) { 30 | updateSources(["app|foo.txt"]); 31 | } 32 | 33 | expectAsset("app|foo.out", "foo.out"); 34 | buildShouldSucceed(); 35 | }); 36 | 37 | test("updates and then removes sources many times", () { 38 | initGraph([ 39 | "app|foo.txt" 40 | ], { 41 | "app": [ 42 | [new RewriteTransformer("txt", "out")] 43 | ] 44 | }); 45 | 46 | for (var i = 0; i < 1000; i++) { 47 | updateSources(["app|foo.txt"]); 48 | removeSources(["app|foo.txt"]); 49 | } 50 | 51 | expectNoAsset("app|foo.out"); 52 | expectNoAsset("app|foo.txt"); 53 | buildShouldSucceed(); 54 | }); 55 | 56 | test("updates transformers many times", () { 57 | var rewrite = new RewriteTransformer("txt", "out"); 58 | initGraph([ 59 | "app|foo.txt" 60 | ], { 61 | "app": [ 62 | [rewrite] 63 | ] 64 | }); 65 | updateSources(["app|foo.txt"]); 66 | 67 | for (var i = 0; i < 1000; i++) { 68 | updateTransformers("app", [ 69 | [rewrite] 70 | ]); 71 | } 72 | 73 | expectAsset("app|foo.out", "foo.out"); 74 | buildShouldSucceed(); 75 | }); 76 | 77 | test("updates and removes transformers many times", () { 78 | var rewrite = new RewriteTransformer("txt", "out"); 79 | initGraph([ 80 | "app|foo.txt" 81 | ], { 82 | "app": [ 83 | [rewrite] 84 | ] 85 | }); 86 | updateSources(["app|foo.txt"]); 87 | 88 | for (var i = 0; i < 1000; i++) { 89 | updateTransformers("app", [ 90 | [rewrite] 91 | ]); 92 | updateTransformers("app", [[]]); 93 | } 94 | 95 | expectAsset("app|foo.txt", "foo"); 96 | expectNoAsset("app|foo.out"); 97 | buildShouldSucceed(); 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /test/multiset_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.multiset_test; 6 | 7 | import 'package:barback/src/utils/multiset.dart'; 8 | import 'package:unittest/unittest.dart'; 9 | 10 | import 'utils.dart'; 11 | 12 | main() { 13 | initConfig(); 14 | 15 | test("new Multiset() creates an empty set", () { 16 | var multiSet = new Multiset(); 17 | expect(multiSet, isEmpty); 18 | expect(multiSet.contains(1), isFalse); 19 | expect(multiSet.count(1), equals(0)); 20 | }); 21 | 22 | test("new Multiset.from(...) constructs a set from the argument", () { 23 | var multiSet = new Multiset.from([1, 2, 3, 2, 4]); 24 | expect(multiSet.toList(), equals([1, 2, 2, 3, 4])); 25 | expect(multiSet.contains(1), isTrue); 26 | expect(multiSet.contains(5), isFalse); 27 | expect(multiSet.count(1), equals(1)); 28 | expect(multiSet.count(2), equals(2)); 29 | expect(multiSet.count(5), equals(0)); 30 | }); 31 | 32 | test("an element can be added and removed once", () { 33 | var multiSet = new Multiset(); 34 | expect(multiSet.contains(1), isFalse); 35 | multiSet.add(1); 36 | expect(multiSet.contains(1), isTrue); 37 | multiSet.remove(1); 38 | expect(multiSet.contains(1), isFalse); 39 | }); 40 | 41 | test("a set can contain multiple copies of an element", () { 42 | var multiSet = new Multiset(); 43 | expect(multiSet.count(1), equals(0)); 44 | multiSet.add(1); 45 | expect(multiSet.count(1), equals(1)); 46 | multiSet.add(1); 47 | expect(multiSet.count(1), equals(2)); 48 | multiSet.remove(1); 49 | expect(multiSet.count(1), equals(1)); 50 | multiSet.remove(1); 51 | expect(multiSet.count(1), equals(0)); 52 | }); 53 | 54 | test("remove returns false if the element wasn't in the set", () { 55 | var multiSet = new Multiset(); 56 | expect(multiSet.remove(1), isFalse); 57 | }); 58 | 59 | test("remove returns true if the element was in the set", () { 60 | var multiSet = new Multiset.from([1]); 61 | expect(multiSet.remove(1), isTrue); 62 | }); 63 | 64 | test( 65 | "remove returns true if the element was in the set even if more copies " 66 | "remain", () { 67 | var multiSet = new Multiset.from([1, 1, 1]); 68 | expect(multiSet.remove(1), isTrue); 69 | }); 70 | 71 | test("iterator orders distinct elements in insertion order", () { 72 | var multiSet = new Multiset()..add(1)..add(2)..add(3)..add(4)..add(5); 73 | expect(multiSet.toList(), equals([1, 2, 3, 4, 5])); 74 | }); 75 | 76 | test("iterator groups multiple copies of an element together", () { 77 | var multiSet = new Multiset()..add(1)..add(2)..add(1)..add(2)..add(1); 78 | expect(multiSet.toList(), equals([1, 1, 1, 2, 2])); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/transformer/aggregate_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.aggregate_transformer; 6 | 7 | import '../asset/asset_id.dart'; 8 | import 'aggregate_transform.dart'; 9 | 10 | /// An alternate interface for transformers that want to perform aggregate 11 | /// transformations on multiple inputs without any individual one of them being 12 | /// considered "primary". 13 | /// 14 | /// This is useful for transformers like image spriting, where all the images in 15 | /// a directory need to be combined into a single image. A normal [Transformer] 16 | /// can't do this gracefully since when it's running on a single image, it has 17 | /// no way of knowing what other images exist to request as secondary inputs. 18 | /// 19 | /// Aggregate transformers work by classifying assets into different groups 20 | /// based on their ids in [classifyPrimary]. Then [apply] is run once for each 21 | /// group. For example, a spriting transformer might put each image asset into a 22 | /// group identified by its directory name. All images in a given directory will 23 | /// end up in the same group, and they'll all be passed to one [apply] call. 24 | /// 25 | /// If possible, aggregate transformers should implement 26 | /// [DeclaringAggregateTransformer] as well to help barback optimize the package 27 | /// graph. 28 | abstract class AggregateTransformer { 29 | /// Classifies an asset id by returning a key identifying which group the 30 | /// asset should be placed in. 31 | /// 32 | /// All assets for which [classifyPrimary] returns the same key are passed 33 | /// together to the same [apply] call. 34 | /// 35 | /// This may return [Future] or, if it's entirely synchronous, 36 | /// [String]. Any string can be used to classify an asset. If possible, 37 | /// though, this should return a path-like string to aid in logging. 38 | /// 39 | /// A return value of `null` indicates that the transformer is not interested 40 | /// in an asset. Assets with a key of `null` will not be passed to any [apply] 41 | /// call; this is equivalent to [Transformer.isPrimary] returning `false`. 42 | classifyPrimary(AssetId id); 43 | 44 | /// Runs this transformer on a group of primary inputs specified by 45 | /// [transform]. 46 | /// 47 | /// If this does asynchronous work, it should return a [Future] that completes 48 | /// once it's finished. 49 | /// 50 | /// This may complete before [AggregateTransform.primarInputs] is closed. For 51 | /// example, it may know that each key will only have two inputs associated 52 | /// with it, and so use `transform.primaryInputs.take(2)` to access only those 53 | /// inputs. 54 | apply(AggregateTransform transform); 55 | 56 | String toString() => runtimeType.toString().replaceAll("Transformer", ""); 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/transformer/declaring_transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.declaring_transform; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import 'declaring_aggregate_transform.dart'; 11 | import 'transform_logger.dart'; 12 | 13 | /// Creates a new [DeclaringTransform] wrapping an 14 | /// [AggregateDeclaringTransform]. 15 | /// 16 | /// Although barback internally works in terms of 17 | /// [DeclaringAggregateTransformer]s, most transformers only work on individual 18 | /// primary inputs in isolation. We want to allow those transformers to 19 | /// implement the more user-friendly [DeclaringTransformer] interface which 20 | /// takes the more user-friendly [DeclaringTransform] object. This method wraps 21 | /// the more general [DeclaringAggregateTransform] to return a 22 | /// [DeclaringTransform] instead. 23 | Future newDeclaringTransform( 24 | DeclaringAggregateTransform aggregate) { 25 | // A wrapped [Transformer] will assign each primary input a unique transform 26 | // key, so we can safely get the first asset emitted. We don't want to wait 27 | // for the stream to close, since that requires barback to prove that no more 28 | // new assets will be generated. 29 | return aggregate.primaryIds.first 30 | .then((primaryId) => new DeclaringTransform._(aggregate, primaryId)); 31 | } 32 | 33 | /// A transform for [DeclaringTransformer]s that allows them to declare the ids 34 | /// of the outputs they'll generate without generating the concrete bodies of 35 | /// those outputs. 36 | class DeclaringTransform { 37 | /// The underlying aggregate transform. 38 | final DeclaringAggregateTransform _aggregate; 39 | 40 | final AssetId primaryId; 41 | 42 | /// A logger so that the [Transformer] can report build details. 43 | TransformLogger get logger => _aggregate.logger; 44 | 45 | DeclaringTransform._(this._aggregate, this.primaryId); 46 | 47 | /// Stores [id] as the id of an output that will be created by this 48 | /// transformation when it's run. 49 | /// 50 | /// A transformation can declare as many assets as it wants. If 51 | /// [DeclaringTransformer.declareOutputs] declareds a given asset id for a 52 | /// given input, [Transformer.apply] should emit the corresponding asset as 53 | /// well. 54 | void declareOutput(AssetId id) => _aggregate.declareOutput(id); 55 | 56 | /// Consume the primary input so that it doesn't get processed by future 57 | /// phases or emitted once processing has finished. 58 | /// 59 | /// Normally the primary input will automatically be forwarded unless the 60 | /// transformer overwrites it by emitting an input with the same id. This 61 | /// allows the transformer to tell barback not to forward the primary input 62 | /// even if it's not overwritten. 63 | void consumePrimary() => _aggregate.consumePrimary(primaryId); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/graph/group_runner.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.group_runner; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_node.dart'; 10 | import '../log.dart'; 11 | import '../transformer/transformer_group.dart'; 12 | import '../utils/stream_pool.dart'; 13 | import 'node_status.dart'; 14 | import 'phase.dart'; 15 | 16 | /// A class that processes all of the phases in a single transformer group. 17 | /// 18 | /// A group takes many inputs, processes them, and emits many outputs. 19 | class GroupRunner { 20 | /// The group this runner runs. 21 | final TransformerGroup _group; 22 | 23 | /// A string describing the location of [this] in the transformer graph. 24 | final String _location; 25 | 26 | /// The phases defined by this group. 27 | final _phases = new List(); 28 | 29 | /// How far along [this] is in processing its assets. 30 | NodeStatus get status { 31 | // Just check the last phase, since it will check all the previous phases 32 | // itself. 33 | return _phases.last.status; 34 | } 35 | 36 | /// A stream that emits an event every time the group's status changes. 37 | Stream get onStatusChange => _onStatusChange; 38 | Stream _onStatusChange; 39 | 40 | /// A stream that emits any new assets emitted by [this]. 41 | /// 42 | /// Assets are emitted synchronously to ensure that any changes are thoroughly 43 | /// propagated as soon as they occur. 44 | Stream get onAsset => _onAsset; 45 | Stream _onAsset; 46 | 47 | /// A stream that emits an event whenever any transforms in this group logs 48 | /// an entry. 49 | Stream get onLog => _onLogPool.stream; 50 | final _onLogPool = new StreamPool.broadcast(); 51 | 52 | GroupRunner(Phase previous, this._group, this._location) { 53 | _addPhase(previous.addPhase(_location), []); 54 | for (var phase in _group.phases) { 55 | _addPhase(_phases.last.addPhase(), phase); 56 | } 57 | 58 | _onAsset = _phases.last.onAsset; 59 | _onStatusChange = _phases.last.onStatusChange; 60 | } 61 | 62 | /// Add a phase with [contents] to [this]'s list of phases. 63 | /// 64 | /// [contents] should be an inner [Iterable] from a [TransformGroup.phases] 65 | /// value. 66 | void _addPhase(Phase phase, Iterable contents) { 67 | _phases.add(phase); 68 | _onLogPool.add(phase.onLog); 69 | phase.updateTransformers(contents); 70 | } 71 | 72 | /// Force all [LazyTransformer]s' transforms in this group to begin producing 73 | /// concrete assets. 74 | void forceAllTransforms() { 75 | for (var phase in _phases) { 76 | phase.forceAllTransforms(); 77 | } 78 | } 79 | 80 | /// Removes this group and all sub-phases within it. 81 | void remove() { 82 | _onLogPool.close(); 83 | for (var phase in _phases) { 84 | phase.remove(); 85 | } 86 | } 87 | 88 | String toString() => "group in phase $_location for $_group"; 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/transformer/transform_logger.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.transform_logger; 6 | 7 | import 'package:source_span/source_span.dart'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import '../log.dart'; 11 | 12 | typedef void LogFunction( 13 | AssetId asset, LogLevel level, String message, SourceSpan span); 14 | 15 | /// Object used to report warnings and errors encountered while running a 16 | /// transformer. 17 | class TransformLogger { 18 | final LogFunction _logFunction; 19 | 20 | TransformLogger(this._logFunction); 21 | 22 | /// Logs an informative message. 23 | /// 24 | /// If [asset] is provided, the log entry is associated with that asset. 25 | /// Otherwise it's associated with the primary input of [transformer]. If 26 | /// present, [span] indicates the location in the input asset that caused the 27 | /// error. 28 | void info(String message, {AssetId asset, SourceSpan span}) { 29 | _logFunction(asset, LogLevel.INFO, message, span); 30 | } 31 | 32 | /// Logs a message that won't be displayed unless the user is running in 33 | /// verbose mode. 34 | /// 35 | /// If [asset] is provided, the log entry is associated with that asset. 36 | /// Otherwise it's associated with the primary input of [transformer]. If 37 | /// present, [span] indicates the location in the input asset that caused the 38 | /// error. 39 | void fine(String message, {AssetId asset, SourceSpan span}) { 40 | _logFunction(asset, LogLevel.FINE, message, span); 41 | } 42 | 43 | /// Logs a warning message. 44 | /// 45 | /// If [asset] is provided, the log entry is associated with that asset. 46 | /// Otherwise it's associated with the primary input of [transformer]. If 47 | /// present, [span] indicates the location in the input asset that caused the 48 | /// error. 49 | void warning(String message, {AssetId asset, SourceSpan span}) { 50 | _logFunction(asset, LogLevel.WARNING, message, span); 51 | } 52 | 53 | /// Logs an error message. 54 | /// 55 | /// If [asset] is provided, the log entry is associated with that asset. 56 | /// Otherwise it's associated with the primary input of [transformer]. If 57 | /// present, [span] indicates the location in the input asset that caused the 58 | /// error. 59 | /// 60 | /// Logging any errors will cause Barback to consider the transformation to 61 | /// have failed, much like throwing an exception. This means that neither the 62 | /// primary input nor any outputs emitted by the transformer will be passed on 63 | /// to the following phase, and the build will be reported as having failed. 64 | /// 65 | /// Unlike throwing an exception, this doesn't cause a transformer to stop 66 | /// running. This makes it useful in cases where a single input may have 67 | /// multiple errors that the user wants to know about. 68 | void error(String message, {AssetId asset, SourceSpan span}) { 69 | _logFunction(asset, LogLevel.ERROR, message, span); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/transformer/wrapping_aggregate_transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.wrapping_aggregate_transformer; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import 'aggregate_transform.dart'; 11 | import 'aggregate_transformer.dart'; 12 | import 'declaring_aggregate_transform.dart'; 13 | import 'declaring_aggregate_transformer.dart'; 14 | import 'declaring_transform.dart'; 15 | import 'declaring_transformer.dart'; 16 | import 'lazy_aggregate_transformer.dart'; 17 | import 'lazy_transformer.dart'; 18 | import 'transform.dart'; 19 | import 'transformer.dart'; 20 | 21 | /// An [AggregateTransformer] that wraps a non-aggregate [Transformer]. 22 | /// 23 | /// Although barback internally works in terms of [AggregateTransformer]s, most 24 | /// transformers only work on individual primary inputs in isolation. We want to 25 | /// allow those transformers to implement the more user-friendly [Transformer] 26 | /// interface. This class makes that possible. 27 | class WrappingAggregateTransformer implements AggregateTransformer { 28 | /// The wrapped transformer. 29 | final Transformer transformer; 30 | 31 | factory WrappingAggregateTransformer(Transformer transformer) { 32 | if (transformer is LazyTransformer) { 33 | return new _LazyWrappingAggregateTransformer( 34 | transformer as LazyTransformer); 35 | } else if (transformer is DeclaringTransformer) { 36 | return new _DeclaringWrappingAggregateTransformer( 37 | transformer as DeclaringTransformer); 38 | } else { 39 | return new WrappingAggregateTransformer._(transformer); 40 | } 41 | } 42 | 43 | WrappingAggregateTransformer._(this.transformer); 44 | 45 | Future classifyPrimary(AssetId id) { 46 | return new Future.sync(() => transformer.isPrimary(id)) 47 | .then((isPrimary) => isPrimary ? id.path : null); 48 | } 49 | 50 | Future apply(AggregateTransform aggregateTransform) { 51 | return newTransform(aggregateTransform) 52 | .then((transform) => transformer.apply(transform)); 53 | } 54 | 55 | String toString() => transformer.toString(); 56 | } 57 | 58 | /// A wrapper for [DeclaringTransformer]s that implements 59 | /// [DeclaringAggregateTransformer]. 60 | class _DeclaringWrappingAggregateTransformer 61 | extends WrappingAggregateTransformer 62 | implements DeclaringAggregateTransformer { 63 | _DeclaringWrappingAggregateTransformer(DeclaringTransformer transformer) 64 | : super._(transformer as Transformer); 65 | 66 | Future declareOutputs(DeclaringAggregateTransform aggregateTransform) { 67 | return newDeclaringTransform(aggregateTransform).then((transform) { 68 | return (transformer as DeclaringTransformer).declareOutputs(transform); 69 | }); 70 | } 71 | } 72 | 73 | /// A wrapper for [LazyTransformer]s that implements 74 | /// [LazyAggregateTransformer]. 75 | class _LazyWrappingAggregateTransformer 76 | extends _DeclaringWrappingAggregateTransformer 77 | implements LazyAggregateTransformer { 78 | _LazyWrappingAggregateTransformer(LazyTransformer transformer) 79 | : super(transformer); 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/graph/static_asset_cascade.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.static_asset_cascade; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:async/async.dart'; 10 | import 'package:collection/collection.dart'; 11 | 12 | import '../asset/asset_id.dart'; 13 | import '../asset/asset_node.dart'; 14 | import '../asset/asset_set.dart'; 15 | import '../errors.dart'; 16 | import '../log.dart'; 17 | import '../package_provider.dart'; 18 | import 'asset_cascade.dart'; 19 | import 'node_status.dart'; 20 | import 'package_graph.dart'; 21 | 22 | /// An asset cascade for a static package. 23 | /// 24 | /// A static package is known to have no transformers and no changes to its 25 | /// assets. This allows this class to lazily and efficiently provide assets to 26 | /// the rest of the package graph. 27 | class StaticAssetCascade implements AssetCascade { 28 | final String package; 29 | 30 | final PackageGraph graph; 31 | 32 | /// All sources that have been requested from the provider. 33 | final _sources = new Map>(); 34 | 35 | StaticAssetCascade(this.graph, this.package); 36 | 37 | Stream get errors => _errorsController.stream; 38 | final _errorsController = 39 | new StreamController.broadcast(sync: true); 40 | 41 | final status = NodeStatus.IDLE; 42 | 43 | final Stream onLog = new StreamController.broadcast().stream; 44 | final Stream onStatusChange = 45 | new StreamController.broadcast().stream; 46 | final Stream onAsset = new StreamController.broadcast().stream; 47 | 48 | Future get availableOutputs { 49 | var provider = graph.provider as StaticPackageProvider; 50 | return provider 51 | .getAllAssetIds(package) 52 | .asyncMap(provider.getAsset) 53 | .toList() 54 | .then((assets) => new AssetSet.from(DelegatingList.typed(assets))); 55 | } 56 | 57 | Future getAssetNode(AssetId id) { 58 | return _sources.putIfAbsent(id, () { 59 | return DelegatingFuture.typed(graph.provider.getAsset(id).then((asset) { 60 | return new AssetNodeController.available(asset).node; 61 | }).catchError((error, stackTrace) { 62 | if (error is! AssetNotFoundException) { 63 | reportError(new AssetLoadException(id, error, stackTrace)); 64 | } 65 | 66 | // TODO(nweiz): propagate error information through asset nodes. 67 | return null; 68 | })); 69 | }); 70 | } 71 | 72 | void updateSources(Iterable sources) => 73 | throw new UnsupportedError("Static package $package can't be explicitly " 74 | "provided sources."); 75 | 76 | void removeSources(Iterable sources) => 77 | throw new UnsupportedError("Static package $package can't be explicitly " 78 | "provided sources."); 79 | 80 | void updateTransformers(Iterable transformersIterable) => 81 | throw new UnsupportedError("Static package $package can't have " 82 | "transformers."); 83 | 84 | void forceAllTransforms() {} 85 | 86 | void reportError(BarbackException error) => _errorsController.add(error); 87 | 88 | String toString() => "static cascade for $package"; 89 | } 90 | -------------------------------------------------------------------------------- /test/stream_replayer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.stream_replayer_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/src/utils.dart'; 10 | import 'package:barback/src/utils/stream_replayer.dart'; 11 | import 'package:unittest/unittest.dart'; 12 | 13 | import 'utils.dart'; 14 | 15 | main() { 16 | initConfig(); 17 | 18 | test( 19 | "a replay that's retrieved before the stream is finished replays the " 20 | "stream", () { 21 | var controller = new StreamController(); 22 | var replay = new StreamReplayer(controller.stream).getReplay(); 23 | 24 | controller.add(1); 25 | controller.add(2); 26 | controller.add(3); 27 | controller.close(); 28 | 29 | expect(replay.toList(), completion(equals([1, 2, 3]))); 30 | }); 31 | 32 | test( 33 | "a replay that's retrieved after the stream is finished replays the " 34 | "stream", () { 35 | var controller = new StreamController(); 36 | var replayer = new StreamReplayer(controller.stream); 37 | 38 | controller.add(1); 39 | controller.add(2); 40 | controller.add(3); 41 | controller.close(); 42 | 43 | expect(replayer.getReplay().toList(), completion(equals([1, 2, 3]))); 44 | }); 45 | 46 | test("multiple replays each replay the stream", () { 47 | var controller = new StreamController(); 48 | var replayer = new StreamReplayer(controller.stream); 49 | 50 | var replay1 = replayer.getReplay(); 51 | controller.add(1); 52 | controller.add(2); 53 | controller.add(3); 54 | controller.close(); 55 | var replay2 = replayer.getReplay(); 56 | 57 | expect(replay1.toList(), completion(equals([1, 2, 3]))); 58 | expect(replay2.toList(), completion(equals([1, 2, 3]))); 59 | }); 60 | 61 | test("the replayed stream doesn't close until the source stream closes", () { 62 | var controller = new StreamController(); 63 | var replay = new StreamReplayer(controller.stream).getReplay(); 64 | var isClosed = false; 65 | replay.last.then((_) { 66 | isClosed = true; 67 | }); 68 | 69 | controller.add(1); 70 | controller.add(2); 71 | controller.add(3); 72 | 73 | expect( 74 | pumpEventQueue().then((_) { 75 | expect(isClosed, isFalse); 76 | controller.close(); 77 | return pumpEventQueue(); 78 | }).then((_) { 79 | expect(isClosed, isTrue); 80 | }), 81 | completes); 82 | }); 83 | 84 | test("the wrapped stream isn't opened if there are no replays", () { 85 | var isOpened = false; 86 | var controller = new StreamController(onListen: () { 87 | isOpened = true; 88 | }); 89 | new StreamReplayer(controller.stream); 90 | 91 | expect(pumpEventQueue().then((_) => isOpened), completion(isFalse)); 92 | }); 93 | 94 | test("the wrapped stream isn't opened if no replays are opened", () { 95 | var isOpened = false; 96 | var controller = new StreamController(onListen: () { 97 | isOpened = true; 98 | }); 99 | var replayer = new StreamReplayer(controller.stream); 100 | replayer.getReplay(); 101 | replayer.getReplay(); 102 | 103 | expect(pumpEventQueue().then((_) => isOpened), completion(isFalse)); 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /test/package_graph/get_all_assets_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.barback_test; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:barback/barback.dart'; 10 | import 'package:scheduled_test/scheduled_test.dart'; 11 | 12 | import '../utils.dart'; 13 | 14 | main() { 15 | initConfig(); 16 | 17 | test("gets all source assets", () { 18 | initGraph(["app|a.txt", "app|b.txt", "app|c.txt"]); 19 | updateSources(["app|a.txt", "app|b.txt", "app|c.txt"]); 20 | expectAllAssets(["app|a.txt", "app|b.txt", "app|c.txt"]); 21 | buildShouldSucceed(); 22 | }); 23 | 24 | test("includes transformed outputs", () { 25 | initGraph([ 26 | "app|a.txt", 27 | "app|foo.blub" 28 | ], { 29 | "app": [ 30 | [new RewriteTransformer("blub", "blab")] 31 | ] 32 | }); 33 | updateSources(["app|a.txt", "app|foo.blub"]); 34 | expectAllAssets(["app|a.txt", "app|foo.blub", "app|foo.blab"]); 35 | buildShouldSucceed(); 36 | }); 37 | 38 | test("includes overwritten outputs", () { 39 | initGraph([ 40 | "app|a.txt", 41 | "app|foo.blub" 42 | ], { 43 | "app": [ 44 | [new RewriteTransformer("blub", "blub")] 45 | ] 46 | }); 47 | updateSources(["app|a.txt", "app|foo.blub"]); 48 | expectAllAssets({"app|a.txt": "a", "app|foo.blub": "foo.blub"}); 49 | buildShouldSucceed(); 50 | }); 51 | 52 | test("completes to an error if two transformers output the same file", () { 53 | initGraph([ 54 | "app|foo.a" 55 | ], { 56 | "app": [ 57 | [new RewriteTransformer("a", "b"), new RewriteTransformer("a", "b")] 58 | ] 59 | }); 60 | updateSources(["app|foo.a"]); 61 | expectAllAssetsShouldFail(isAssetCollisionException("app|foo.b")); 62 | }); 63 | 64 | test("completes to an error if a transformer fails", () { 65 | initGraph([ 66 | "app|foo.txt" 67 | ], { 68 | "app": [ 69 | [ 70 | new BadTransformer(["app|foo.out"]) 71 | ] 72 | ] 73 | }); 74 | 75 | updateSources(["app|foo.txt"]); 76 | expectAllAssetsShouldFail( 77 | isTransformerException(equals(BadTransformer.ERROR))); 78 | }); 79 | 80 | test("completes to an aggregate error if there are multiple errors", () { 81 | initGraph([ 82 | "app|foo.txt" 83 | ], { 84 | "app": [ 85 | [ 86 | new BadTransformer(["app|foo.out"]), 87 | new BadTransformer(["app|foo.out2"]) 88 | ] 89 | ] 90 | }); 91 | 92 | updateSources(["app|foo.txt"]); 93 | expectAllAssetsShouldFail(isAggregateException([ 94 | isTransformerException(equals(BadTransformer.ERROR)), 95 | isTransformerException(equals(BadTransformer.ERROR)) 96 | ])); 97 | }); 98 | 99 | // Regression test. 100 | test( 101 | "getAllAssets() is called synchronously after after initializing " 102 | "barback", () { 103 | var provider = new MockProvider( 104 | {"app|a.txt": "a", "app|b.txt": "b", "app|c.txt": "c"}); 105 | var barback = new Barback(provider); 106 | barback.updateSources([ 107 | new AssetId.parse("app|a.txt"), 108 | new AssetId.parse("app|b.txt"), 109 | new AssetId.parse("app|c.txt") 110 | ]); 111 | 112 | expect( 113 | barback.getAllAssets().then((assets) { 114 | return Future.wait(assets.map((asset) => asset.readAsString())); 115 | }), 116 | completion(unorderedEquals(["a", "b", "c"]))); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /lib/src/transformer/transformer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.transformer; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import '../utils.dart'; 11 | import 'transform.dart'; 12 | 13 | /// A [Transformer] represents a processor that takes in one or more input 14 | /// assets and uses them to generate one or more output assets. 15 | /// 16 | /// Dart2js, a SASS->CSS processor, a CSS spriter, and a tool to concatenate 17 | /// files are all examples of transformers. To define your own transformation 18 | /// step, extend (or implement) this class. 19 | /// 20 | /// If possible, transformers should implement [DeclaringTransformer] as well to 21 | /// help barback optimize the package graph. 22 | abstract class Transformer { 23 | /// Override this to return a space-separated list of file extensions that are 24 | /// allowed for the primary inputs to this transformer. 25 | /// 26 | /// Each extension must begin with a leading `.`. 27 | /// 28 | /// If you don't override [isPrimary] yourself, it defaults to allowing any 29 | /// asset whose extension matches one of the ones returned by this. If you 30 | /// don't override [isPrimary] *or* this, it allows all files. 31 | String get allowedExtensions => null; 32 | 33 | Transformer() { 34 | if (allowedExtensions == null) return; 35 | 36 | var invalidExtensions = allowedExtensions 37 | .split(" ") 38 | .where((extension) => !extension.startsWith(".")) 39 | .map((extension) => '"$extension"'); 40 | if (invalidExtensions.isEmpty) return; 41 | 42 | throw new FormatException('Each extension in $this.allowedExtensions ' 43 | 'must begin with a ".", but ${toSentence(invalidExtensions)} ' 44 | '${pluralize("doesn't", invalidExtensions.length, plural: "don't")}.'); 45 | } 46 | 47 | /// Returns `true` if [id] can be a primary input for this transformer. 48 | /// 49 | /// While a transformer can read from multiple input files, one must be the 50 | /// "primary" input. This asset determines whether the transformation should 51 | /// be run at all. If the primary input is removed, the transformer will no 52 | /// longer be run. 53 | /// 54 | /// A concrete example is dart2js. When you run dart2js, it will traverse 55 | /// all of the imports in your Dart source files and use the contents of all 56 | /// of those to generate the final JS. However you still run dart2js "on" a 57 | /// single file: the entrypoint Dart file that has your `main()` method. 58 | /// This entrypoint file would be the primary input. 59 | /// 60 | /// If this is not overridden, defaults to allow any asset whose extension 61 | /// matches one of the ones returned by [allowedExtensions]. If *that* is 62 | /// not overridden, allows all assets. 63 | /// 64 | /// This may return a `Future` or, if it's entirely synchronous, a 65 | /// `bool`. 66 | isPrimary(AssetId id) { 67 | // Allow all files if [primaryExtensions] is not overridden. 68 | if (allowedExtensions == null) return true; 69 | 70 | for (var extension in allowedExtensions.split(" ")) { 71 | if (id.path.endsWith(extension)) return true; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | /// Run this transformer on the primary input specified by [transform]. 78 | /// 79 | /// The [transform] is used by the [Transformer] for two purposes (in 80 | /// addition to accessing the primary input). It can call `getInput()` to 81 | /// request additional input assets. It also calls `addOutput()` to provide 82 | /// generated assets back to the system. Either can be called multiple times, 83 | /// in any order. 84 | /// 85 | /// In other words, a Transformer's job is to find all inputs for a 86 | /// transform, starting at the primary input, then generate all output assets 87 | /// and yield them back to the transform. 88 | /// 89 | /// If this does asynchronous work, it should return a [Future] that completes 90 | /// once it's finished. 91 | apply(Transform transform); 92 | 93 | String toString() => runtimeType.toString().replaceAll("Transformer", ""); 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/transformer/declaring_aggregate_transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.declaring_aggregate_transform; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import '../graph/transform_node.dart'; 11 | import '../utils.dart'; 12 | import 'base_transform.dart'; 13 | 14 | /// A transform for [DeclaringAggregateTransformer]s that allows them to declare 15 | /// the ids of the outputs they'll generate without generating the concrete 16 | /// bodies of those outputs. 17 | class DeclaringAggregateTransform extends BaseTransform { 18 | /// The set of output ids declared by the transformer. 19 | final _outputIds = new Set(); 20 | 21 | /// The transform key. 22 | /// 23 | /// This is the key returned by [AggregateTransformer.classifyPrimary] for all 24 | /// the assets in this transform. 25 | final String key; 26 | 27 | /// The package in which this transform is running. 28 | final String package; 29 | 30 | /// The stream of primary input ids that have been aggregated for this 31 | /// transform. 32 | /// 33 | /// This is exposed as a stream so that the transformer can start working 34 | /// before all its input ids are available. The stream is closed not just when 35 | /// all inputs are provided, but when barback is confident no more inputs will 36 | /// be forthcoming. 37 | /// 38 | /// A transformer may complete its `declareOutputs` method before this stream 39 | /// is closed. For example, it may know that each key will only have two 40 | /// inputs associated with it, and so use `transform.primaryIds.take(2)` to 41 | /// access only those inputs' ids. 42 | Stream get primaryIds => _primaryIds; 43 | Stream _primaryIds; 44 | 45 | /// The controller for [primaryIds]. 46 | /// 47 | /// This is a broadcast controller so that the transform can keep 48 | /// [_emittedPrimaryIds] up to date. 49 | final _idController = new StreamController.broadcast(); 50 | 51 | /// The set of all primary input ids that have been emitted by [primaryIds]. 52 | final _emittedPrimaryIds = new Set(); 53 | 54 | DeclaringAggregateTransform._(TransformNode node) 55 | : key = node.key, 56 | package = node.phase.cascade.package, 57 | super(node) { 58 | _idController.stream.listen(_emittedPrimaryIds.add); 59 | // [primaryIds] should be a non-broadcast stream. 60 | _primaryIds = broadcastToSingleSubscription(_idController.stream); 61 | } 62 | 63 | /// Stores [id] as the id of an output that will be created by this 64 | /// transformation when it's run. 65 | /// 66 | /// A transformation can declare as many assets as it wants. If 67 | /// [DeclaringTransformer.declareOutputs] declares a given asset id for a 68 | /// given input, [Transformer.apply] should emit the corresponding asset as 69 | /// well. 70 | void declareOutput(AssetId id) { 71 | // TODO(nweiz): This should immediately throw if an output with that ID 72 | // has already been declared by this transformer. 73 | _outputIds.add(id); 74 | } 75 | 76 | void consumePrimary(AssetId id) { 77 | if (!_emittedPrimaryIds.contains(id)) { 78 | throw new StateError( 79 | "$id can't be consumed because it's not a primary input."); 80 | } 81 | 82 | super.consumePrimary(id); 83 | } 84 | } 85 | 86 | /// The controller for [DeclaringAggregateTransform]. 87 | class DeclaringAggregateTransformController extends BaseTransformController { 88 | final DeclaringAggregateTransform transform; 89 | 90 | /// The set of ids that the transformer declares it will emit. 91 | Set get outputIds => transform._outputIds; 92 | 93 | bool get isDone => transform._idController.isClosed; 94 | 95 | DeclaringAggregateTransformController(TransformNode node) 96 | : transform = new DeclaringAggregateTransform._(node); 97 | 98 | /// Adds a primary input id to the [DeclaringAggregateTransform.primaryIds] 99 | /// stream. 100 | void addId(AssetId id) => transform._idController.add(id); 101 | 102 | void done() { 103 | transform._idController.close(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/asset/asset_id.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.asset_id; 6 | 7 | import 'package:path/path.dart' as pathos; 8 | 9 | /// Identifies an asset within a package. 10 | class AssetId implements Comparable { 11 | /// The name of the package containing this asset. 12 | final String package; 13 | 14 | /// The path to the asset relative to the root directory of [package]. 15 | /// 16 | /// Source (i.e. read from disk) and generated (i.e. the output of a 17 | /// [Transformer]) assets all have paths. Even intermediate assets that are 18 | /// generated and then consumed by later transformations will still have 19 | /// a path used to identify it. 20 | /// 21 | /// Asset paths always use forward slashes as path separators, regardless of 22 | /// the host platform. 23 | final String path; 24 | 25 | /// Gets the file extension of the asset, if it has one, including the ".". 26 | String get extension => pathos.extension(path); 27 | 28 | /// Creates a new AssetId at [path] within [package]. 29 | /// 30 | /// The [path] will be normalized: any backslashes will be replaced with 31 | /// forward slashes (regardless of host OS) and "." and ".." will be removed 32 | /// where possible. 33 | AssetId(this.package, String path) : path = _normalizePath(path); 34 | 35 | /// Parses an [AssetId] string of the form "package|path/to/asset.txt". 36 | /// 37 | /// The [path] will be normalized: any backslashes will be replaced with 38 | /// forward slashes (regardless of host OS) and "." and ".." will be removed 39 | /// where possible. 40 | factory AssetId.parse(String description) { 41 | var parts = description.split("|"); 42 | if (parts.length != 2) { 43 | throw new FormatException('Could not parse "$description".'); 44 | } 45 | 46 | if (parts[0].isEmpty) { 47 | throw new FormatException( 48 | 'Cannot have empty package name in "$description".'); 49 | } 50 | 51 | if (parts[1].isEmpty) { 52 | throw new FormatException('Cannot have empty path in "$description".'); 53 | } 54 | 55 | return new AssetId(parts[0], parts[1]); 56 | } 57 | 58 | /// Deserializes an [AssetId] from [data], which must be the result of 59 | /// calling [serialize] on an existing [AssetId]. 60 | /// 61 | /// Note that this is intended for communicating ids across isolates and not 62 | /// for persistent storage of asset identifiers. There is no guarantee of 63 | /// backwards compatibility in serialization form across versions. 64 | AssetId.deserialize(data) 65 | : package = data[0], 66 | path = data[1]; 67 | 68 | /// Returns `true` of [other] is an [AssetId] with the same package and path. 69 | operator ==(other) => 70 | other is AssetId && package == other.package && path == other.path; 71 | 72 | int get hashCode => package.hashCode ^ path.hashCode; 73 | 74 | int compareTo(AssetId other) { 75 | var packageComp = package.compareTo(other.package); 76 | if (packageComp != 0) return packageComp; 77 | return path.compareTo(other.path); 78 | } 79 | 80 | /// Returns a new [AssetId] with the same [package] as this one and with the 81 | /// [path] extended to include [extension]. 82 | AssetId addExtension(String extension) => 83 | new AssetId(package, "$path$extension"); 84 | 85 | /// Returns a new [AssetId] with the same [package] and [path] as this one 86 | /// but with file extension [newExtension]. 87 | AssetId changeExtension(String newExtension) => 88 | new AssetId(package, pathos.withoutExtension(path) + newExtension); 89 | 90 | String toString() => "$package|$path"; 91 | 92 | /// Serializes this [AssetId] to an object that can be sent across isolates 93 | /// and passed to [deserialize]. 94 | serialize() => [package, path]; 95 | } 96 | 97 | String _normalizePath(String path) { 98 | if (pathos.isAbsolute(path)) { 99 | throw new ArgumentError('Asset paths must be relative, but got "$path".'); 100 | } 101 | 102 | // Normalize path separators so that they are always "/" in the AssetID. 103 | path = path.replaceAll(r"\", "/"); 104 | 105 | // Collapse "." and "..". 106 | return pathos.posix.normalize(path); 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/serialize.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.serialize; 6 | 7 | import 'dart:async'; 8 | import 'dart:isolate'; 9 | 10 | import 'package:stack_trace/stack_trace.dart'; 11 | 12 | import 'asset/asset_id.dart'; 13 | import 'utils.dart'; 14 | 15 | /// Converts [id] into a serializable map. 16 | Map serializeId(AssetId id) => {'package': id.package, 'path': id.path}; 17 | 18 | /// Converts [stream] into a [SendPort] with which another isolate can request 19 | /// the data from [stream]. 20 | SendPort serializeStream(Stream stream) { 21 | var receivePort = new ReceivePort(); 22 | receivePort.first.then((sendPort) { 23 | stream.listen((data) => sendPort.send({'type': 'data', 'data': data}), 24 | onDone: () => sendPort.send({'type': 'done'}), 25 | onError: (error, stackTrace) { 26 | sendPort.send({ 27 | 'type': 'error', 28 | 'error': CrossIsolateException.serialize(error, stackTrace) 29 | }); 30 | }); 31 | }); 32 | 33 | return receivePort.sendPort; 34 | } 35 | 36 | /// Converts a serializable map into an [AssetId]. 37 | AssetId deserializeId(Map id) => new AssetId(id['package'], id['path']); 38 | 39 | /// Convert a [SendPort] whose opposite is waiting to send us a stream into a 40 | /// [Stream]. 41 | /// 42 | /// No stream data will actually be sent across the isolate boundary until 43 | /// someone subscribes to the returned stream. 44 | Stream deserializeStream(SendPort sendPort) { 45 | return callbackStream(() { 46 | var receivePort = new ReceivePort(); 47 | sendPort.send(receivePort.sendPort); 48 | return receivePort 49 | .transform(const StreamTransformer(_deserializeTransformer)); 50 | }); 51 | } 52 | 53 | /// The body of a [StreamTransformer] that deserializes the values in a stream 54 | /// sent by [serializeStream]. 55 | StreamSubscription _deserializeTransformer(Stream input, bool cancelOnError) { 56 | var subscription; 57 | var transformed = input 58 | .transform(new StreamTransformer.fromHandlers(handleData: (data, sink) { 59 | if (data['type'] == 'data') { 60 | sink.add(data['data']); 61 | } else if (data['type'] == 'error') { 62 | var exception = new CrossIsolateException.deserialize(data['error']); 63 | sink.addError(exception, exception.stackTrace); 64 | } else { 65 | assert(data['type'] == 'done'); 66 | sink.close(); 67 | subscription.cancel(); 68 | } 69 | })); 70 | subscription = transformed.listen(null, cancelOnError: cancelOnError); 71 | return subscription; 72 | } 73 | 74 | /// An exception that was originally raised in another isolate. 75 | /// 76 | /// Exception objects can't cross isolate boundaries in general, so this class 77 | /// wraps as much information as can be consistently serialized. 78 | class CrossIsolateException implements Exception { 79 | /// The name of the type of exception thrown. 80 | /// 81 | /// This is the return value of [error.runtimeType.toString()]. Keep in mind 82 | /// that objects in different libraries may have the same type name. 83 | final String type; 84 | 85 | /// The exception's message, or its [toString] if it didn't expose a `message` 86 | /// property. 87 | final String message; 88 | 89 | /// The exception's stack chain, or `null` if no stack chain was available. 90 | final Chain stackTrace; 91 | 92 | /// Loads a [CrossIsolateException] from a serialized representation. 93 | /// 94 | /// [error] should be the result of [CrossIsolateException.serialize]. 95 | CrossIsolateException.deserialize(Map error) 96 | : type = error['type'], 97 | message = error['message'], 98 | stackTrace = 99 | error['stack'] == null ? null : new Chain.parse(error['stack']); 100 | 101 | /// Serializes [error] to an object that can safely be passed across isolate 102 | /// boundaries. 103 | static Map serialize(error, [StackTrace stack]) { 104 | if (stack == null && error is Error) stack = error.stackTrace; 105 | return { 106 | 'type': error.runtimeType.toString(), 107 | 'message': getErrorMessage(error), 108 | 'stack': stack == null ? null : new Chain.forTrace(stack).toString() 109 | }; 110 | } 111 | 112 | String toString() => "$message\n$stackTrace"; 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/graph/phase_output.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.phase_output; 6 | 7 | import 'dart:async'; 8 | import 'dart:collection'; 9 | 10 | import '../asset/asset_forwarder.dart'; 11 | import '../asset/asset_node.dart'; 12 | import '../errors.dart'; 13 | import 'phase.dart'; 14 | 15 | /// A class that handles a single output of a phase. 16 | /// 17 | /// Normally there's only a single [AssetNode] for a phase's output, but it's 18 | /// possible that multiple transformers in the same phase emit assets with the 19 | /// same id, causing collisions. This handles those collisions by forwarding the 20 | /// chronologically first asset. 21 | /// 22 | /// When the asset being forwarding changes, the old value of [output] will be 23 | /// marked as removed and a new value will replace it. Users of this class can 24 | /// be notified of this using [onAsset]. 25 | class PhaseOutput { 26 | /// The phase for which this is an output. 27 | final Phase _phase; 28 | 29 | /// A string describing the location of [this] in the transformer graph. 30 | final String _location; 31 | 32 | /// The asset node for this output. 33 | AssetNode get output => _outputForwarder.node; 34 | AssetForwarder _outputForwarder; 35 | 36 | /// A stream that emits an [AssetNode] each time this output starts forwarding 37 | /// a new asset. 38 | Stream get onAsset => _onAssetController.stream; 39 | final _onAssetController = 40 | new StreamController.broadcast(sync: true); 41 | 42 | /// The assets for this output. 43 | /// 44 | /// If there's no collision, this will only have one element. Otherwise, it 45 | /// will be ordered by which asset was added first. 46 | final _assets = new Queue(); 47 | 48 | /// The [AssetCollisionException] for this output, or null if there is no 49 | /// collision currently. 50 | AssetCollisionException get collisionException { 51 | if (_assets.length == 1) return null; 52 | return new AssetCollisionException( 53 | _assets 54 | .where((asset) => asset.transform != null) 55 | .map((asset) => asset.transform.info), 56 | output.id); 57 | } 58 | 59 | PhaseOutput(this._phase, AssetNode output, this._location) 60 | : _outputForwarder = new AssetForwarder(output) { 61 | assert(!output.state.isRemoved); 62 | add(output); 63 | } 64 | 65 | /// Adds an asset node as an output with this id. 66 | void add(AssetNode node) { 67 | assert(node.id == output.id); 68 | assert(!output.state.isRemoved); 69 | _assets.add(node); 70 | _watchAsset(node); 71 | } 72 | 73 | /// Removes all existing listeners on [output] without actually closing 74 | /// [this]. 75 | /// 76 | /// This marks [output] as removed, but immediately replaces it with a new 77 | /// [AssetNode] in the same state as the old output. This is used when adding 78 | /// a new [Phase] to cause consumers of the prior phase's outputs to be to 79 | /// start consuming the new phase's outputs instead. 80 | void removeListeners() { 81 | _outputForwarder.close(); 82 | _outputForwarder = new AssetForwarder(_assets.first); 83 | _onAssetController.add(output); 84 | } 85 | 86 | /// Watches [node] to adjust [_assets] and [output] when it's removed. 87 | void _watchAsset(AssetNode node) { 88 | node.whenRemoved(() { 89 | if (_assets.length == 1) { 90 | assert(_assets.single == node); 91 | _outputForwarder.close(); 92 | _onAssetController.close(); 93 | return; 94 | } 95 | 96 | // If there was more than one asset, we're resolving a collision -- 97 | // possibly partially. 98 | var wasFirst = _assets.first == node; 99 | _assets.remove(node); 100 | 101 | // If this was the first asset, we replace it with the next asset 102 | // (chronologically). 103 | if (wasFirst) removeListeners(); 104 | 105 | // If there's still a collision, report it. This lets the user know if 106 | // they've successfully resolved the collision or not. 107 | if (_assets.length > 1) { 108 | // TODO(nweiz): report this through the output asset. 109 | _phase.cascade.reportError(collisionException); 110 | } 111 | }); 112 | } 113 | 114 | String toString() => "phase output in $_location for $output"; 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/transformer/base_transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.base_transform; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_id.dart'; 10 | import '../graph/transform_node.dart'; 11 | import '../log.dart'; 12 | import 'transform_logger.dart'; 13 | 14 | /// The base class for the ephemeral transform objects that are passed to 15 | /// transformers. 16 | /// 17 | /// This class provides the transformers with inputs, but its up to the 18 | /// subclasses to provide a means of emitting outputs. 19 | abstract class BaseTransform { 20 | final TransformNode _node; 21 | 22 | /// The ids of primary inputs that should be consumed. 23 | /// 24 | /// This is exposed by [BaseTransformController]. 25 | final _consumedPrimaries = new Set(); 26 | 27 | /// Whether the transformer logged an error. 28 | /// 29 | /// This is exposed via [BaseTransformController]. 30 | bool _loggedError = false; 31 | 32 | /// The controller for the stream of log entries emitted by the transformer. 33 | /// 34 | /// This is exposed via [BaseTransformController]. 35 | /// 36 | /// This is synchronous because error logs can cause the transform to fail, so 37 | /// we need to ensure that their processing isn't delayed until after the 38 | /// transform or build has finished. 39 | final _onLogController = new StreamController.broadcast(sync: true); 40 | 41 | /// A logger so that the [Transformer] can report build details. 42 | TransformLogger get logger => _logger; 43 | TransformLogger _logger; 44 | 45 | BaseTransform(this._node) { 46 | _logger = new TransformLogger((asset, level, message, span) { 47 | if (level == LogLevel.ERROR) _loggedError = true; 48 | 49 | // If the log isn't already associated with an asset, use the primary. 50 | if (asset == null) asset = _node.info.primaryId; 51 | var entry = new LogEntry(_node.info, asset, level, message, span); 52 | 53 | // The log controller can be closed while log entries are still coming in 54 | // if the transformer is removed during [apply]. 55 | if (!_onLogController.isClosed) _onLogController.add(entry); 56 | }); 57 | } 58 | 59 | /// Consume a primary input so that it doesn't get processed by future 60 | /// phases or emitted once processing has finished. 61 | /// 62 | /// Normally each primary input will automatically be forwarded unless the 63 | /// transformer overwrites it by emitting an input with the same id. This 64 | /// allows the transformer to tell barback not to forward a primary input 65 | /// even if it's not overwritten. 66 | void consumePrimary(AssetId id) { 67 | // TODO(nweiz): throw an error if an id is consumed that wasn't listed as a 68 | // primary input. 69 | _consumedPrimaries.add(id); 70 | } 71 | } 72 | 73 | /// The base class for controllers of subclasses of [BaseTransform]. 74 | /// 75 | /// Controllers are used so that [TransformNode]s can get values from a 76 | /// [BaseTransform] without exposing getters in the public API. 77 | abstract class BaseTransformController { 78 | /// The [BaseTransform] controlled by this controller. 79 | BaseTransform get transform; 80 | 81 | /// The ids of primary inputs that should be consumed. 82 | Set get consumedPrimaries => transform._consumedPrimaries; 83 | 84 | /// Whether the transform logged an error. 85 | bool get loggedError => transform._loggedError; 86 | 87 | /// The stream of log entries emitted by the transformer during a run. 88 | Stream get onLog => transform._onLogController.stream; 89 | 90 | /// Whether the transform's input or id stream has been closed. 91 | /// 92 | /// See also [done]. 93 | bool get isDone; 94 | 95 | /// Mark this transform as finished emitting new inputs or input ids. 96 | /// 97 | /// This is distinct from [cancel] in that it *doesn't* indicate that the 98 | /// transform is finished being used entirely. The transformer may still log 99 | /// messages and load secondary inputs. This just indicates that all the 100 | /// primary inputs are accounted for. 101 | void done(); 102 | 103 | /// Mark this transform as canceled. 104 | /// 105 | /// This will close any streams and release any resources that were allocated 106 | /// for the duration of the transformation. Unlike [done], this indicates that 107 | /// the transformation is no longer relevant; either it has returned, or 108 | /// something external has preemptively invalidated its results. 109 | void cancel() { 110 | done(); 111 | transform._onLogController.close(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/transformer/transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.transform; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | 10 | import '../asset/asset.dart'; 11 | import '../asset/asset_id.dart'; 12 | import '../errors.dart'; 13 | import 'aggregate_transform.dart'; 14 | import 'transform_logger.dart'; 15 | 16 | /// Creates a new [Transform] wrapping an [AggregateTransform]. 17 | /// 18 | /// Although barback internally works in terms of [AggregateTransformer]s, most 19 | /// transformers only work on individual primary inputs in isolation. We want to 20 | /// allow those transformers to implement the more user-friendly [Transformer] 21 | /// interface which takes the more user-friendly [Transform] object. This method 22 | /// wraps the more general [AggregateTransform] to return a [Transform] instead. 23 | Future newTransform(AggregateTransform aggregate) { 24 | // A wrapped [Transformer] will assign each primary input a unique transform 25 | // key, so we can safely get the first asset emitted. We don't want to wait 26 | // for the stream to close, since that requires barback to prove that no more 27 | // new assets will be generated. 28 | return aggregate.primaryInputs.first 29 | .then((primaryInput) => new Transform._(aggregate, primaryInput)); 30 | } 31 | 32 | /// While a [Transformer] represents a *kind* of transformation, this defines 33 | /// one specific usage of it on a set of files. 34 | /// 35 | /// This ephemeral object exists only during an actual transform application to 36 | /// facilitate communication between the [Transformer] and the code hosting 37 | /// the transformation. It lets the [Transformer] access inputs and generate 38 | /// outputs. 39 | class Transform { 40 | /// The underlying aggregate transform. 41 | final AggregateTransform _aggregate; 42 | 43 | /// Gets the primary input asset. 44 | /// 45 | /// While a transformation can use multiple input assets, one must be a 46 | /// special "primary" asset. This will be the "entrypoint" or "main" input 47 | /// file for a transformation. 48 | /// 49 | /// For example, with a dart2js transform, the primary input would be the 50 | /// entrypoint Dart file. All of the other Dart files that that imports 51 | /// would be secondary inputs. 52 | final Asset primaryInput; 53 | 54 | /// A logger so that the [Transformer] can report build details. 55 | TransformLogger get logger => _aggregate.logger; 56 | 57 | Transform._(this._aggregate, this.primaryInput); 58 | 59 | /// Gets the asset for an input [id]. 60 | /// 61 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 62 | Future getInput(AssetId id) => _aggregate.getInput(id); 63 | 64 | /// A convenience method to the contents of the input with [id] as a string. 65 | /// 66 | /// This is equivalent to calling [getInput] followed by [Asset.readAsString]. 67 | /// 68 | /// If the asset was created from a [String] the original string is always 69 | /// returned and [encoding] is ignored. Otherwise, the binary data of the 70 | /// asset is decoded using [encoding], which defaults to [utf8]. 71 | /// 72 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 73 | Future readInputAsString(AssetId id, {Encoding encoding}) => 74 | _aggregate.readInputAsString(id, encoding: encoding); 75 | 76 | /// A convenience method to the contents of the input with [id]. 77 | /// 78 | /// This is equivalent to calling [getInput] followed by [Asset.read]. 79 | /// 80 | /// If the asset was created from a [String], this returns its UTF-8 encoding. 81 | /// 82 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 83 | Stream> readInput(AssetId id) => _aggregate.readInput(id); 84 | 85 | /// A convenience method to return whether or not an asset exists. 86 | /// 87 | /// This is equivalent to calling [getInput] and catching an 88 | /// [AssetNotFoundException]. 89 | Future hasInput(AssetId id) => _aggregate.hasInput(id); 90 | 91 | /// Stores [output] as an output created by this transformation. 92 | /// 93 | /// A transformation can output as many assets as it wants. 94 | void addOutput(Asset output) => _aggregate.addOutput(output); 95 | 96 | /// Consume the primary input so that it doesn't get processed by future 97 | /// phases or emitted once processing has finished. 98 | /// 99 | /// Normally the primary input will automatically be forwarded unless the 100 | /// transformer overwrites it by emitting an input with the same id. This 101 | /// allows the transformer to tell barback not to forward the primary input 102 | /// even if it's not overwritten. 103 | void consumePrimary() => _aggregate.consumePrimary(primaryInput.id); 104 | } 105 | -------------------------------------------------------------------------------- /test/package_graph/source_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.package_graph.source_test; 6 | 7 | import 'package:scheduled_test/scheduled_test.dart'; 8 | 9 | import '../utils.dart'; 10 | 11 | main() { 12 | initConfig(); 13 | test("gets a source asset", () { 14 | initGraph(["app|foo.txt"]); 15 | updateSources(["app|foo.txt"]); 16 | expectAsset("app|foo.txt"); 17 | buildShouldSucceed(); 18 | }); 19 | 20 | test("doesn't get an unknown source", () { 21 | initGraph(); 22 | expectNoAsset("app|unknown.txt"); 23 | }); 24 | 25 | test("doesn't get an unprovided source", () { 26 | initGraph(); 27 | updateSources(["app|unknown.txt"]); 28 | expectNoAsset("app|unknown.txt"); 29 | }); 30 | 31 | test("doesn't get an asset that isn't an updated source", () { 32 | initGraph(["app|foo.txt"]); 33 | 34 | // Sources must be explicitly made visible to barback by calling 35 | // updateSources() on them. It isn't enough for the provider to be able 36 | // to provide it. 37 | // 38 | // This lets you distinguish between sources that you want to be primaries 39 | // and the larger set of inputs that those primaries are allowed to pull in. 40 | expectNoAsset("app|foo.txt"); 41 | }); 42 | 43 | test("gets a source asset if not transformed", () { 44 | initGraph([ 45 | "app|foo.txt" 46 | ], { 47 | "app": [ 48 | [new RewriteTransformer("nottxt", "whatever")] 49 | ] 50 | }); 51 | 52 | updateSources(["app|foo.txt"]); 53 | expectAsset("app|foo.txt"); 54 | buildShouldSucceed(); 55 | }); 56 | 57 | test("doesn't get a removed source", () { 58 | initGraph(["app|foo.txt"]); 59 | 60 | updateSources(["app|foo.txt"]); 61 | expectAsset("app|foo.txt"); 62 | buildShouldSucceed(); 63 | 64 | removeSources(["app|foo.txt"]); 65 | expectNoAsset("app|foo.txt"); 66 | buildShouldSucceed(); 67 | }); 68 | 69 | test("collapses redundant updates", () { 70 | var transformer = new RewriteTransformer("blub", "blab"); 71 | initGraph([ 72 | "app|foo.blub" 73 | ], { 74 | "app": [ 75 | [transformer] 76 | ] 77 | }); 78 | 79 | schedule(() { 80 | // Make a bunch of synchronous update calls. 81 | updateSourcesSync(["app|foo.blub"]); 82 | updateSourcesSync(["app|foo.blub"]); 83 | updateSourcesSync(["app|foo.blub"]); 84 | updateSourcesSync(["app|foo.blub"]); 85 | }); 86 | 87 | expectAsset("app|foo.blab", "foo.blab"); 88 | buildShouldSucceed(); 89 | 90 | expect(transformer.numRuns, completion(equals(1))); 91 | }); 92 | 93 | test("a removal cancels out an update", () { 94 | initGraph(["app|foo.txt"]); 95 | 96 | schedule(() { 97 | updateSourcesSync(["app|foo.txt"]); 98 | removeSourcesSync(["app|foo.txt"]); 99 | }); 100 | 101 | expectNoAsset("app|foo.txt"); 102 | buildShouldSucceed(); 103 | }); 104 | 105 | test("an update cancels out a removal", () { 106 | initGraph(["app|foo.txt"]); 107 | 108 | schedule(() { 109 | removeSourcesSync(["app|foo.txt"]); 110 | updateSourcesSync(["app|foo.txt"]); 111 | }); 112 | 113 | expectAsset("app|foo.txt"); 114 | buildShouldSucceed(); 115 | }); 116 | 117 | test("reloads an asset that's updated while loading", () { 118 | initGraph({"app|foo.txt": "foo"}); 119 | 120 | pauseProvider(); 121 | // The mock provider synchronously loads the value of the assets, so this 122 | // will kick off two loads with different values. The second one should 123 | // win. 124 | updateSources(["app|foo.txt"]); 125 | modifyAsset("app|foo.txt", "bar"); 126 | updateSources(["app|foo.txt"]); 127 | 128 | resumeProvider(); 129 | expectAsset("app|foo.txt", "bar"); 130 | buildShouldSucceed(); 131 | }); 132 | 133 | test("restarts a build if a source is updated while sources are loading", () { 134 | var transformer = new RewriteTransformer("txt", "out"); 135 | initGraph([ 136 | "app|foo.txt", 137 | "app|other.bar" 138 | ], { 139 | "app": [ 140 | [transformer] 141 | ] 142 | }); 143 | 144 | // Run the whole graph so all nodes are clean. 145 | updateSources(["app|foo.txt", "app|other.bar"]); 146 | expectAsset("app|foo.out", "foo.out"); 147 | expectAsset("app|other.bar"); 148 | 149 | buildShouldSucceed(); 150 | 151 | // Make the provider slow to load a source. 152 | pauseProvider(); 153 | 154 | // Update an asset that doesn't trigger any transformers. 155 | updateSources(["app|other.bar"]); 156 | 157 | // Now update an asset that does trigger a transformer. 158 | updateSources(["app|foo.txt"]); 159 | 160 | resumeProvider(); 161 | 162 | buildShouldSucceed(); 163 | 164 | expect(transformer.numRuns, completion(equals(2))); 165 | }); 166 | } 167 | -------------------------------------------------------------------------------- /lib/src/barback.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.barback; 6 | 7 | import 'dart:async'; 8 | 9 | import 'asset/asset.dart'; 10 | import 'asset/asset_id.dart'; 11 | import 'asset/asset_set.dart'; 12 | import 'log.dart'; 13 | import 'build_result.dart'; 14 | import 'errors.dart'; 15 | import 'graph/package_graph.dart'; 16 | import 'package_provider.dart'; 17 | import 'transformer/transformer.dart'; 18 | 19 | /// A general-purpose asynchronous build dependency graph manager. 20 | /// 21 | /// It consumes source assets (including Dart files) in a set of packages, 22 | /// runs transformations on them, and then tracks which sources have been 23 | /// modified and which transformations need to be re-run. 24 | /// 25 | /// To do this, you give barback a [PackageProvider] which can yield a set of 26 | /// [Transformer]s and raw source [Asset]s. Then you tell it which input files 27 | /// have been added or modified by calling [updateSources]. Barback will 28 | /// automatically wire up the appropriate transformers to those inputs and 29 | /// start running them asynchronously. If a transformer produces outputs that 30 | /// can be consumed by other transformers, they will automatically be pipelined 31 | /// correctly. 32 | /// 33 | /// You can then request assets (either source or generated) by calling 34 | /// [getAssetById]. This will wait for any necessary transformations and then 35 | /// return the asset. 36 | /// 37 | /// When source files have been modified or removed, tell barback by calling 38 | /// [updateSources] and [removeSources] as appropriate. Barback will 39 | /// automatically track which transformations are affected by those changes and 40 | /// re-run them as needed. 41 | /// 42 | /// Barback tries to be resilient to errors since assets are often in an 43 | /// in-progress state. When errors occur, they will be captured and emitted on 44 | /// the [errors] stream. 45 | class Barback { 46 | /// The graph managed by this instance. 47 | final PackageGraph _graph; 48 | 49 | /// A stream that emits a [BuildResult] each time the build is completed, 50 | /// whether or not it succeeded. 51 | /// 52 | /// This will emit a result only once every package's [AssetCascade] has 53 | /// finished building. 54 | /// 55 | /// If an unexpected error in barback itself occurs, it will be emitted 56 | /// through this stream's error channel. 57 | Stream get results => _graph.results; 58 | 59 | /// A stream that emits any errors from the graph or the transformers. 60 | /// 61 | /// This emits errors as they're detected. If an error occurs in one part of 62 | /// the graph, unrelated parts will continue building. 63 | /// 64 | /// This will not emit programming errors from barback itself. Those will be 65 | /// emitted through the [results] stream's error channel. 66 | Stream get errors => _graph.errors; 67 | 68 | /// The stream of [LogEntry] objects used to report transformer log entries. 69 | /// 70 | /// If this stream has listeners, then log entries will go to that. 71 | /// Otherwise, a default logger will display them. 72 | Stream get log => _graph.log; 73 | 74 | Barback(PackageProvider provider) : _graph = new PackageGraph(provider); 75 | 76 | /// Gets the asset identified by [id]. 77 | /// 78 | /// If [id] is for a generated or transformed asset, this will wait until 79 | /// it has been created and return it. If the asset cannot be found, throws 80 | /// [AssetNotFoundException]. 81 | Future getAssetById(AssetId id) { 82 | return _graph.getAssetNode(id).then((node) { 83 | if (node == null) throw new AssetNotFoundException(id); 84 | return node.asset; 85 | }); 86 | } 87 | 88 | /// Adds [sources] to the graph's known set of source assets. 89 | /// 90 | /// Begins applying any transforms that can consume any of the sources. If a 91 | /// given source is already known, it is considered modified and all 92 | /// transforms that use it will be re-applied. 93 | void updateSources(Iterable sources) => 94 | _graph.updateSources(sources); 95 | 96 | /// Removes [removed] from the graph's known set of source assets. 97 | void removeSources(Iterable removed) => 98 | _graph.removeSources(removed); 99 | 100 | /// Gets all output assets. 101 | /// 102 | /// If a build is currently in progress, waits until it completes. The 103 | /// returned future will complete with a [BarbackException] if the build is 104 | /// not successful. 105 | Future getAllAssets() => _graph.getAllAssets(); 106 | 107 | /// Sets the transformer phases for [package]'s assets to [transformers]. 108 | /// 109 | /// To the extent that [transformers] is similar to the previous transformer 110 | /// phases for [package], the existing asset graph will be preserved. 111 | /// 112 | /// Elements of the inner iterable of [transformers] must be [Transformer]s, 113 | /// [TransformerGroup]s, or [AggregateTransformer]s. 114 | void updateTransformers(String package, Iterable transformers) => 115 | _graph.updateTransformers(package, transformers); 116 | } 117 | -------------------------------------------------------------------------------- /test/asset_set_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.asset_set_test; 6 | 7 | import 'package:barback/barback.dart'; 8 | import 'package:unittest/unittest.dart'; 9 | 10 | import 'utils.dart'; 11 | 12 | main() { 13 | initConfig(); 14 | 15 | var fooId = new AssetId.parse("app|foo.txt"); 16 | var barId = new AssetId.parse("app|bar.txt"); 17 | var bazId = new AssetId.parse("app|baz.txt"); 18 | 19 | group(".from()", () { 20 | test("creates a set from an iterable", () { 21 | var set = new AssetSet.from([ 22 | new Asset.fromString(fooId, "foo"), 23 | new Asset.fromString(barId, "bar") 24 | ]); 25 | 26 | expect(set.containsId(fooId), isTrue); 27 | expect(set.containsId(barId), isTrue); 28 | expect(set.containsId(bazId), isFalse); 29 | }); 30 | }); 31 | 32 | group("[] operator", () { 33 | test("gets an asset with the given ID", () { 34 | var set = new AssetSet(); 35 | var foo = new Asset.fromString(fooId, "foo"); 36 | set.add(foo); 37 | 38 | expect(set[fooId], equals(foo)); 39 | }); 40 | 41 | test("returns null if no asset with the ID is in the set", () { 42 | var set = new AssetSet(); 43 | expect(set[fooId], isNull); 44 | }); 45 | }); 46 | 47 | group(".add()", () { 48 | test("adds the asset to the set", () { 49 | var set = new AssetSet(); 50 | var foo = new Asset.fromString(fooId, "foo"); 51 | set.add(foo); 52 | expect(set.contains(foo), isTrue); 53 | }); 54 | 55 | test("replaces a previously added asset with that ID", () { 56 | var set = new AssetSet(); 57 | set.add(new Asset.fromString(fooId, "before")); 58 | set.add(new Asset.fromString(fooId, "after")); 59 | expect(set[fooId].readAsString(), completion(equals("after"))); 60 | }); 61 | 62 | test("returns the added item", () { 63 | var set = new AssetSet(); 64 | var foo = new Asset.fromString(fooId, "foo"); 65 | expect(set.add(foo), equals(foo)); 66 | }); 67 | }); 68 | 69 | group(".addAll()", () { 70 | test("adds the assets to the set", () { 71 | var set = new AssetSet(); 72 | var foo = new Asset.fromString(fooId, "foo"); 73 | var bar = new Asset.fromString(barId, "bar"); 74 | set.addAll([foo, bar]); 75 | expect(set.contains(foo), isTrue); 76 | expect(set.contains(bar), isTrue); 77 | }); 78 | 79 | test("replaces assets earlier in the sequence with later ones", () { 80 | var set = new AssetSet(); 81 | var foo1 = new Asset.fromString(fooId, "before"); 82 | var foo2 = new Asset.fromString(fooId, "after"); 83 | set.addAll([foo1, foo2]); 84 | expect(set[fooId].readAsString(), completion(equals("after"))); 85 | }); 86 | }); 87 | 88 | group(".clear()", () { 89 | test("empties the set", () { 90 | var set = new AssetSet(); 91 | var foo = new Asset.fromString(fooId, "foo"); 92 | set.add(foo); 93 | set.clear(); 94 | 95 | expect(set.length, equals(0)); 96 | expect(set.contains(foo), isFalse); 97 | }); 98 | }); 99 | 100 | group(".contains()", () { 101 | test("returns true if the asset is in the set", () { 102 | var set = new AssetSet(); 103 | var foo = new Asset.fromString(fooId, "foo"); 104 | var bar = new Asset.fromString(barId, "bar"); 105 | set.add(foo); 106 | 107 | expect(set.contains(foo), isTrue); 108 | expect(set.contains(bar), isFalse); 109 | }); 110 | }); 111 | 112 | group(".containsId()", () { 113 | test("returns true if an asset with the ID is in the set", () { 114 | var set = new AssetSet(); 115 | var foo = new Asset.fromString(fooId, "foo"); 116 | set.add(foo); 117 | 118 | expect(set.containsId(fooId), isTrue); 119 | expect(set.containsId(barId), isFalse); 120 | }); 121 | }); 122 | 123 | group(".removeId()", () { 124 | test("removes the asset with the ID from the set", () { 125 | var set = new AssetSet(); 126 | var foo = new Asset.fromString(fooId, "foo"); 127 | set.add(foo); 128 | 129 | set.removeId(fooId); 130 | expect(set.containsId(fooId), isFalse); 131 | }); 132 | 133 | test("returns the removed asset", () { 134 | var set = new AssetSet(); 135 | var foo = new Asset.fromString(fooId, "foo"); 136 | set.add(foo); 137 | 138 | expect(set.removeId(fooId).readAsString(), completion(equals("foo"))); 139 | }); 140 | 141 | test("returns null when removing an asset not in the set", () { 142 | var set = new AssetSet(); 143 | var foo = new Asset.fromString(fooId, "foo"); 144 | set.add(foo); 145 | 146 | expect(set.removeId(barId), isNull); 147 | }); 148 | }); 149 | 150 | group(".ids", () { 151 | test("contains the ids of all the assets in the set", () { 152 | var set = new AssetSet(); 153 | var foo = new Asset.fromString(fooId, "foo"); 154 | var bar = new Asset.fromString(barId, "bar"); 155 | set.addAll([foo, bar]); 156 | expect(set.ids, unorderedEquals([fooId, barId])); 157 | }); 158 | }); 159 | } 160 | -------------------------------------------------------------------------------- /test/package_graph/transform/consume_input_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.test.package_graph.transform.pass_through_test; 6 | 7 | import 'package:scheduled_test/scheduled_test.dart'; 8 | 9 | import '../../utils.dart'; 10 | 11 | main() { 12 | initConfig(); 13 | test("a transform consumes its input without overwriting it", () { 14 | initGraph([ 15 | "app|foo.txt" 16 | ], { 17 | "app": [ 18 | [new RewriteTransformer("txt", "out")..consumePrimary = true] 19 | ] 20 | }); 21 | 22 | updateSources(["app|foo.txt"]); 23 | expectAsset("app|foo.out", "foo.out"); 24 | expectNoAsset("app|foo.txt"); 25 | buildShouldSucceed(); 26 | }); 27 | 28 | test("a transform consumes its input while a sibling overwrites it", () { 29 | initGraph([ 30 | "app|foo.txt" 31 | ], { 32 | "app": [ 33 | [ 34 | new RewriteTransformer("txt", "out")..consumePrimary = true, 35 | new RewriteTransformer("txt", "txt") 36 | ] 37 | ] 38 | }); 39 | 40 | updateSources(["app|foo.txt"]); 41 | expectAsset("app|foo.out", "foo.out"); 42 | expectAsset("app|foo.txt", "foo.txt"); 43 | buildShouldSucceed(); 44 | }); 45 | 46 | test("a transform stops consuming its input", () { 47 | initGraph({ 48 | "app|foo.txt": "yes" 49 | }, { 50 | "app": [ 51 | [new ConditionallyConsumePrimaryTransformer("txt", "out", "yes")] 52 | ] 53 | }); 54 | 55 | updateSources(["app|foo.txt"]); 56 | expectAsset("app|foo.out", "yes.out"); 57 | expectNoAsset("app|foo.txt"); 58 | buildShouldSucceed(); 59 | 60 | modifyAsset("app|foo.txt", "no"); 61 | updateSources(["app|foo.txt"]); 62 | expectAsset("app|foo.out", "no.out"); 63 | expectAsset("app|foo.txt", "no"); 64 | buildShouldSucceed(); 65 | }); 66 | 67 | test("two sibling transforms both consume their input", () { 68 | initGraph([ 69 | "app|foo.txt" 70 | ], { 71 | "app": [ 72 | [ 73 | new RewriteTransformer("txt", "one")..consumePrimary = true, 74 | new RewriteTransformer("txt", "two")..consumePrimary = true 75 | ] 76 | ] 77 | }); 78 | 79 | updateSources(["app|foo.txt"]); 80 | expectAsset("app|foo.one", "foo.one"); 81 | expectAsset("app|foo.two", "foo.two"); 82 | expectNoAsset("app|foo.txt"); 83 | buildShouldSucceed(); 84 | }); 85 | 86 | test( 87 | "a transform stops consuming its input but a sibling is still " 88 | "consuming it", () { 89 | initGraph({ 90 | "app|foo.txt": "yes" 91 | }, { 92 | "app": [ 93 | [ 94 | new RewriteTransformer("txt", "one")..consumePrimary = true, 95 | new ConditionallyConsumePrimaryTransformer("txt", "two", "yes") 96 | ] 97 | ] 98 | }); 99 | 100 | updateSources(["app|foo.txt"]); 101 | expectAsset("app|foo.one", "yes.one"); 102 | expectAsset("app|foo.two", "yes.two"); 103 | expectNoAsset("app|foo.txt"); 104 | buildShouldSucceed(); 105 | 106 | modifyAsset("app|foo.txt", "no"); 107 | updateSources(["app|foo.txt"]); 108 | expectAsset("app|foo.one", "no.one"); 109 | expectAsset("app|foo.two", "no.two"); 110 | expectNoAsset("app|foo.txt"); 111 | buildShouldSucceed(); 112 | }); 113 | 114 | test("a transform consumes its input and emits nothing", () { 115 | initGraph([ 116 | "app|foo.txt" 117 | ], { 118 | "app": [ 119 | [new EmitNothingTransformer("txt")..consumePrimary = true] 120 | ] 121 | }); 122 | 123 | updateSources(["app|foo.txt"]); 124 | expectNoAsset("app|foo.txt"); 125 | buildShouldSucceed(); 126 | }); 127 | 128 | test("a transform consumes its input, then is removed", () { 129 | initGraph([ 130 | "app|foo.txt" 131 | ], { 132 | "app": [ 133 | [new RewriteTransformer("txt", "out")..consumePrimary = true] 134 | ] 135 | }); 136 | 137 | updateSources(["app|foo.txt"]); 138 | expectAsset("app|foo.out", "foo.out"); 139 | expectNoAsset("app|foo.txt"); 140 | buildShouldSucceed(); 141 | 142 | updateTransformers("app", [[]]); 143 | expectNoAsset("app|foo.out"); 144 | expectAsset("app|foo.txt", "foo"); 145 | buildShouldSucceed(); 146 | }); 147 | 148 | test("a transform consumes its input and emits nothing, then is removed", () { 149 | initGraph([ 150 | "app|foo.txt" 151 | ], { 152 | "app": [ 153 | [new EmitNothingTransformer("txt")..consumePrimary = true] 154 | ] 155 | }); 156 | 157 | updateSources(["app|foo.txt"]); 158 | expectNoAsset("app|foo.txt"); 159 | buildShouldSucceed(); 160 | 161 | updateTransformers("app", [[]]); 162 | expectAsset("app|foo.txt", "foo"); 163 | buildShouldSucceed(); 164 | }); 165 | 166 | test("a transform which consumes its input is added", () { 167 | initGraph([ 168 | "app|foo.txt" 169 | ], { 170 | "app": [[]] 171 | }); 172 | 173 | updateSources(["app|foo.txt"]); 174 | expectNoAsset("app|foo.out"); 175 | expectAsset("app|foo.txt", "foo"); 176 | buildShouldSucceed(); 177 | 178 | updateTransformers("app", [ 179 | [new RewriteTransformer("txt", "out")..consumePrimary = true] 180 | ]); 181 | expectAsset("app|foo.out", "foo.out"); 182 | expectNoAsset("app|foo.txt"); 183 | buildShouldSucceed(); 184 | }); 185 | } 186 | -------------------------------------------------------------------------------- /lib/src/graph/phase_forwarder.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.phase_forwarder; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_node.dart'; 10 | import '../asset/asset_node_set.dart'; 11 | 12 | /// A class that takes care of forwarding assets within a phase. 13 | /// 14 | /// Each phase contains one or more channels that process its input assets. Each 15 | /// non-grouped transformer for that phase is a channel, each [TransformerGroup] 16 | /// in that phase is another, and the source node is the final channel. For each 17 | /// input asset, each channel individually decides whether to forward that asset 18 | /// based on whether that channel uses it. If a channel does decide to forward 19 | /// an asset, we call that forwarded asset an "intermediate forwarded asset" to 20 | /// distinguish it from the output of a [PhaseForwarder]. 21 | /// 22 | /// All intermediate assets with a given origin are provided to a single 23 | /// [PhaseForwarder] via [addIntermediateAsset]. This forwarder then determines 24 | /// whether all channels in the phase produced intermediate assets. If so, that 25 | /// means the input asset wasn't consumed by any channel, so the 26 | /// [PhaseForwarder] forwards it again, producing an output which we'll call the 27 | /// "final forwarded asset". 28 | /// 29 | /// A final forwarded asset will be available only if all of the intermediate 30 | /// forwarded assets are themselves available. If any of the intermediate assets 31 | /// are dirty, the final asset will also be marked dirty. 32 | class PhaseForwarder { 33 | /// The number of channels to forward, counting the source node. 34 | int _numChannels; 35 | 36 | /// The intermediate forwarded assets. 37 | final _intermediateAssets = new AssetNodeSet(); 38 | 39 | /// The final forwarded asset. 40 | /// 41 | /// This will be null if the asset is not being forwarded. 42 | AssetNode get output => 43 | _outputController == null ? null : _outputController.node; 44 | AssetNodeController _outputController; 45 | 46 | /// A stream that emits an event whenever [this] starts producing a final 47 | /// forwarded asset. 48 | /// 49 | /// Whenever this stream emits an event, the value will be identical to 50 | /// [output]. 51 | Stream get onAsset => _onAssetController.stream; 52 | final _onAssetController = 53 | new StreamController.broadcast(sync: true); 54 | 55 | /// Creates a phase forwarder forwarding nodes that come from [node] across 56 | /// [numTransformers] transformers and [numGroups] groups. 57 | /// 58 | /// [node] is passed in explicitly so that it can be forwarded if there are no 59 | /// other channels. 60 | PhaseForwarder(AssetNode node, int numTransformers, int numGroups) 61 | : _numChannels = numTransformers + numGroups + 1 { 62 | addIntermediateAsset(node); 63 | } 64 | 65 | /// Notify the forwarder that the number of transformer and group channels has 66 | /// changed. 67 | void updateTransformers(int numTransformers, int numGroups) { 68 | // Add one channel for the source node. 69 | _numChannels = numTransformers + numGroups + 1; 70 | _adjustOutput(); 71 | } 72 | 73 | /// Adds an intermediate forwarded asset to [this]. 74 | /// 75 | /// [asset] must have the same origin as all other intermediate forwarded 76 | /// assets. 77 | void addIntermediateAsset(AssetNode asset) { 78 | if (_intermediateAssets.isNotEmpty) { 79 | assert(asset.origin == _intermediateAssets.first.origin); 80 | } 81 | 82 | _intermediateAssets.add(asset); 83 | asset.onStateChange.listen((_) => _adjustOutput()); 84 | 85 | _adjustOutput(); 86 | } 87 | 88 | /// Mark this forwarder as removed. 89 | /// 90 | /// This will remove [output] if it exists. 91 | void remove() { 92 | if (_outputController != null) { 93 | _outputController.setRemoved(); 94 | _outputController = null; 95 | } 96 | _onAssetController.close(); 97 | } 98 | 99 | /// Adjusts [output] to ensure that it accurately reflects the current state 100 | /// of the intermediate forwarded assets. 101 | void _adjustOutput() { 102 | assert(_intermediateAssets.length <= _numChannels); 103 | assert(!_intermediateAssets.any((asset) => asset.state.isRemoved)); 104 | 105 | // If there are any channels that haven't forwarded an intermediate asset, 106 | // we shouldn't forward a final asset. If we are currently, remove 107 | // it. 108 | if (_intermediateAssets.length < _numChannels) { 109 | if (_outputController == null) return; 110 | _outputController.setRemoved(); 111 | _outputController = null; 112 | return; 113 | } 114 | 115 | // If there isn't a final asset being forwarded yet, we should forward one. 116 | // It should be dirty iff any of the intermediate assets are dirty. 117 | if (_outputController == null) { 118 | var finalAsset = _intermediateAssets.firstWhere( 119 | (asset) => asset.state.isDirty, 120 | orElse: () => _intermediateAssets.first); 121 | _outputController = new AssetNodeController.from(finalAsset); 122 | _onAssetController.add(output); 123 | return; 124 | } 125 | 126 | // If we're already forwarding a final asset, set it dirty iff any of the 127 | // intermediate assets are dirty. 128 | if (_intermediateAssets.any((asset) => asset.state.isDirty)) { 129 | if (!_outputController.node.state.isDirty) _outputController.setDirty(); 130 | } else if (!_outputController.node.state.isAvailable) { 131 | _outputController.setAvailable(_intermediateAssets.first.asset); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/graph/transformer_classifier.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.graph.transformer_classifier; 6 | 7 | import 'dart:async'; 8 | 9 | import '../asset/asset_forwarder.dart'; 10 | import '../asset/asset_node.dart'; 11 | import '../errors.dart'; 12 | import '../log.dart'; 13 | import '../transformer/aggregate_transformer.dart'; 14 | import '../transformer/wrapping_aggregate_transformer.dart'; 15 | import 'node_status.dart'; 16 | import 'node_streams.dart'; 17 | import 'phase.dart'; 18 | import 'transform_node.dart'; 19 | 20 | /// A class for classifying the primary inputs for a transformer according to 21 | /// its [AggregateTransformer.classifyPrimary] method. 22 | /// 23 | /// This is also used for non-aggregate transformers; they're modeled as 24 | /// aggregate transformers that return the primary path if `isPrimary` is true 25 | /// and `null` if `isPrimary` is `null`. 26 | class TransformerClassifier { 27 | /// The containing [Phase]. 28 | final Phase phase; 29 | 30 | /// The [AggregateTransformer] used to classify the inputs. 31 | final AggregateTransformer transformer; 32 | 33 | /// A string describing the location of [this] in the transformer graph. 34 | final String _location; 35 | 36 | /// The individual transforms for each classiciation key. 37 | final _transforms = new Map(); 38 | 39 | /// Forwarders used to pass through assets that aren't used by [transformer]. 40 | final _passThroughForwarders = new Set(); 41 | 42 | /// The streams exposed by this classifier. 43 | final _streams = new NodeStreams(); 44 | Stream get onStatusChange => _streams.onStatusChange; 45 | Stream get onAsset => _streams.onAsset; 46 | Stream get onLog => _streams.onLog; 47 | 48 | /// A broadcast stream that emits an event whenever [this] has finished 49 | /// classifying all available inputs. 50 | Stream get onDoneClassifying => _onDoneClassifyingController.stream; 51 | final _onDoneClassifyingController = 52 | new StreamController.broadcast(sync: true); 53 | 54 | /// The number of currently-active calls to [transformer.classifyPrimary]. 55 | /// 56 | /// This is used to determine whether [this] is dirty. 57 | var _activeClassifications = 0; 58 | 59 | /// Whether this is currently classifying any inputs. 60 | bool get isClassifying => _activeClassifications > 0; 61 | 62 | /// How far along [this] is in processing its assets. 63 | NodeStatus get status { 64 | if (isClassifying) return NodeStatus.RUNNING; 65 | return NodeStatus 66 | .dirtiest(_transforms.values.map((transform) => transform.status)); 67 | } 68 | 69 | TransformerClassifier(this.phase, transformer, this._location) 70 | : transformer = transformer is AggregateTransformer 71 | ? transformer 72 | : new WrappingAggregateTransformer(transformer); 73 | 74 | /// Adds a new asset as an input for this transformer. 75 | void addInput(AssetNode input) { 76 | _activeClassifications++; 77 | new Future.sync(() => transformer.classifyPrimary(input.id)) 78 | .catchError((error, stackTrace) { 79 | if (input.state.isRemoved) return null; 80 | 81 | // Catch all transformer errors and pipe them to the results stream. This 82 | // is so a broken transformer doesn't take down the whole graph. 83 | var info = new TransformInfo(transformer, input.id); 84 | if (error is! AssetNotFoundException) { 85 | error = new TransformerException(info, error, stackTrace); 86 | } else { 87 | error = new MissingInputException(info, error.id); 88 | } 89 | phase.cascade.reportError(error); 90 | 91 | return null; 92 | }).then((key) { 93 | if (input.state.isRemoved) return; 94 | if (key == null) { 95 | var forwarder = new AssetForwarder(input); 96 | _passThroughForwarders.add(forwarder); 97 | forwarder.node 98 | .whenRemoved(() => _passThroughForwarders.remove(forwarder)); 99 | _streams.onAssetController.add(forwarder.node); 100 | } else if (_transforms.containsKey(key)) { 101 | _transforms[key].addPrimary(input); 102 | } else { 103 | var transform = new TransformNode(this, transformer, key, _location); 104 | _transforms[key] = transform; 105 | 106 | transform.onStatusChange.listen((_) => _streams.changeStatus(status), 107 | onDone: () { 108 | _transforms.remove(transform.key); 109 | if (!_streams.isClosed) _streams.changeStatus(status); 110 | }); 111 | 112 | _streams.onAssetPool.add(transform.onAsset); 113 | _streams.onLogPool.add(transform.onLog); 114 | transform.addPrimary(input); 115 | } 116 | }).whenComplete(() { 117 | _activeClassifications--; 118 | if (_streams.isClosed) return; 119 | if (!isClassifying) _onDoneClassifyingController.add(null); 120 | _streams.changeStatus(status); 121 | }); 122 | } 123 | 124 | /// Removes this transformer. 125 | /// 126 | /// This marks all outputs of the transformer as removed. 127 | void remove() { 128 | _streams.close(); 129 | _onDoneClassifyingController.close(); 130 | for (var transform in _transforms.values.toList()) { 131 | transform.remove(); 132 | } 133 | for (var forwarder in _passThroughForwarders.toList()) { 134 | forwarder.close(); 135 | } 136 | } 137 | 138 | /// Force all deferred transforms to begin producing concrete assets. 139 | void forceAllTransforms() { 140 | for (var transform in _transforms.values) { 141 | transform.force(); 142 | } 143 | } 144 | 145 | String toString() => "classifier in $_location for $transformer"; 146 | } 147 | -------------------------------------------------------------------------------- /lib/src/asset/internal_asset.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.asset.internal_asset; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | import 'dart:typed_data'; 10 | 11 | import 'package:async/async.dart'; 12 | import 'package:collection/collection.dart'; 13 | 14 | import '../serialize.dart'; 15 | import '../utils.dart'; 16 | import '../utils/file_pool.dart'; 17 | import '../utils/stream_replayer.dart'; 18 | import 'asset.dart'; 19 | import 'asset_id.dart'; 20 | 21 | /// Serialize an asset to a form that's safe to send across isolates. 22 | Map serializeAsset(Asset asset) { 23 | var id = serializeId(asset.id); 24 | if (asset is BinaryAsset) { 25 | return {'type': 'binary', 'id': id, 'contents': asset._contents}; 26 | } else if (asset is FileAsset) { 27 | return {'type': 'file', 'id': id, 'path': asset._path}; 28 | } else if (asset is StringAsset) { 29 | return {'type': 'string', 'id': id, 'contents': asset._contents}; 30 | } else { 31 | // [asset] is probably a [StreamAsset], but it's possible that the user has 32 | // created a custom subclass, in which case we just serialize the stream 33 | // anyway. 34 | return { 35 | 'type': 'stream', 36 | 'id': id, 37 | 'stream': serializeStream(asset.read()) 38 | }; 39 | } 40 | } 41 | 42 | /// Deserialize an asset from the form returned by [serialize]. 43 | Asset deserializeAsset(Map asset) { 44 | var id = deserializeId(asset['id']); 45 | switch (asset['type']) { 46 | case 'binary': 47 | return new BinaryAsset( 48 | id, DelegatingList.typed(asset['contents'] as List)); 49 | case 'file': 50 | return new FileAsset(id, asset['path']); 51 | case 'string': 52 | return new StringAsset(id, asset['contents']); 53 | case 'stream': 54 | return new StreamAsset( 55 | id, DelegatingStream.typed(deserializeStream(asset['stream']))); 56 | default: 57 | throw new FormatException('Unknown asset type "${asset['type']}".'); 58 | } 59 | } 60 | 61 | /// An asset whose data is stored in a list of bytes. 62 | class BinaryAsset implements Asset { 63 | final AssetId id; 64 | 65 | final Uint8List _contents; 66 | 67 | BinaryAsset(this.id, List contents) : _contents = toUint8List(contents); 68 | 69 | Future readAsString({Encoding encoding}) { 70 | if (encoding == null) encoding = utf8; 71 | 72 | return new Future.value(encoding.decode(_contents)); 73 | } 74 | 75 | Stream> read() => new Future>.value(_contents).asStream(); 76 | 77 | String toString() { 78 | var buffer = new StringBuffer(); 79 | buffer.write("Bytes ["); 80 | 81 | // Don't show the whole list if it's long. 82 | if (_contents.length > 11) { 83 | for (var i = 0; i < 5; i++) { 84 | buffer.write(byteToHex(_contents[i])); 85 | buffer.write(" "); 86 | } 87 | 88 | buffer.write("..."); 89 | 90 | for (var i = _contents.length - 5; i < _contents.length; i++) { 91 | buffer.write(" "); 92 | buffer.write(byteToHex(_contents[i])); 93 | } 94 | } else { 95 | for (var i = 0; i < _contents.length; i++) { 96 | if (i > 0) buffer.write(" "); 97 | buffer.write(byteToHex(_contents[i])); 98 | } 99 | } 100 | 101 | buffer.write("]"); 102 | return buffer.toString(); 103 | } 104 | } 105 | 106 | /// An asset backed by a file on the local file system. 107 | class FileAsset implements Asset { 108 | final AssetId id; 109 | 110 | /// Use a [FilePool] to handle reads so we can try to cope with running out 111 | /// of file descriptors more gracefully. 112 | static final _pool = new FilePool(); 113 | 114 | final String _path; 115 | FileAsset(this.id, this._path); 116 | 117 | Future readAsString({Encoding encoding}) { 118 | if (encoding == null) encoding = utf8; 119 | return _pool.readAsString(_path, encoding); 120 | } 121 | 122 | Stream> read() => _pool.openRead(_path); 123 | 124 | String toString() => 'File "${_path}"'; 125 | } 126 | 127 | /// An asset whose data is stored in a string. 128 | class StringAsset implements Asset { 129 | final AssetId id; 130 | 131 | final String _contents; 132 | 133 | StringAsset(this.id, this._contents); 134 | 135 | Future readAsString({Encoding encoding}) => 136 | new Future.value(_contents); 137 | 138 | Stream> read() => 139 | new Future>.value(utf8.encode(_contents)).asStream(); 140 | 141 | String toString() { 142 | // Don't show the whole string if it's long. 143 | var contents = _contents; 144 | if (contents.length > 40) { 145 | contents = contents.substring(0, 20) + 146 | " ... " + 147 | contents.substring(contents.length - 20); 148 | } 149 | 150 | contents = _escape(contents); 151 | return 'String "$contents"'; 152 | } 153 | 154 | String _escape(String string) { 155 | return string 156 | .replaceAll("\"", r'\"') 157 | .replaceAll("\n", r"\n") 158 | .replaceAll("\r", r"\r") 159 | .replaceAll("\t", r"\t"); 160 | } 161 | } 162 | 163 | /// An asset whose data is available from a stream. 164 | class StreamAsset implements Asset { 165 | final AssetId id; 166 | 167 | /// A stream replayer that records and replays the contents of the input 168 | /// stream. 169 | final StreamReplayer> _replayer; 170 | 171 | StreamAsset(this.id, Stream> stream) 172 | : _replayer = new StreamReplayer(stream); 173 | 174 | Future readAsString({Encoding encoding}) { 175 | if (encoding == null) encoding = utf8; 176 | return _replayer 177 | .getReplay() 178 | .expand((chunk) => chunk) 179 | .toList() 180 | .then((bytes) => encoding.decode(bytes)); 181 | } 182 | 183 | Stream> read() => _replayer.getReplay(); 184 | 185 | String toString() => "Stream"; 186 | } 187 | -------------------------------------------------------------------------------- /lib/src/transformer/aggregate_transform.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library barback.transformer.aggregate_transform; 6 | 7 | import 'dart:async'; 8 | import 'dart:convert'; 9 | 10 | import 'package:async/async.dart'; 11 | 12 | import '../asset/asset.dart'; 13 | import '../asset/asset_id.dart'; 14 | import '../asset/asset_set.dart'; 15 | import '../errors.dart'; 16 | import '../graph/transform_node.dart'; 17 | import '../utils.dart'; 18 | import 'base_transform.dart'; 19 | 20 | /// A transform for [AggregateTransformer]s that provides access to all of their 21 | /// primary inputs. 22 | class AggregateTransform extends BaseTransform { 23 | final TransformNode _node; 24 | 25 | /// The set of outputs emitted by the transformer. 26 | final _outputs = new AssetSet(); 27 | 28 | /// The transform key. 29 | /// 30 | /// This is the key returned by [AggregateTransformer.classifyPrimary] for all 31 | /// the assets in this transform. 32 | String get key => _node.key; 33 | 34 | /// The package in which this transform is running. 35 | String get package => _node.phase.cascade.package; 36 | 37 | /// The stream of primary inputs that will be processed by this transform. 38 | /// 39 | /// This is exposed as a stream so that the transformer can start working 40 | /// before all its inputs are available. The stream is closed not just when 41 | /// all inputs are provided, but when barback is confident no more inputs will 42 | /// be forthcoming. 43 | /// 44 | /// A transformer may complete its `apply` method before this stream is 45 | /// closed. For example, it may know that each key will only have two inputs 46 | /// associated with it, and so use `transform.primaryInputs.take(2)` to access 47 | /// only those inputs. 48 | Stream get primaryInputs => _inputController.stream; 49 | final _inputController = new StreamController(); 50 | 51 | /// The set of all primary inputs that have been emitted by [primaryInputs]. 52 | /// 53 | /// This is populated by the transform's controller so that 54 | /// [AggregateTransformController.addedId] synchronously returns the correct 55 | /// result after [AggregateTransformController.addInput] is called. 56 | final _emittedPrimaryInputs = new AssetSet(); 57 | 58 | AggregateTransform._(TransformNode node) 59 | : _node = node, 60 | super(node); 61 | 62 | /// Gets the asset for an input [id]. 63 | /// 64 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 65 | Future getInput(AssetId id) { 66 | if (_emittedPrimaryInputs.containsId(id)) { 67 | return new Future.sync(() => _emittedPrimaryInputs[id]); 68 | } else { 69 | return _node.getInput(id); 70 | } 71 | } 72 | 73 | /// A convenience method to the contents of the input with [id] as a string. 74 | /// 75 | /// This is equivalent to calling [getInput] followed by [Asset.readAsString]. 76 | /// 77 | /// If the asset was created from a [String] the original string is always 78 | /// returned and [encoding] is ignored. Otherwise, the binary data of the 79 | /// asset is decoded using [encoding], which defaults to [utf8]. 80 | /// 81 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 82 | Future readInputAsString(AssetId id, {Encoding encoding}) { 83 | if (encoding == null) encoding = utf8; 84 | return getInput(id).then( 85 | (input) => input.readAsString(encoding: encoding)); 86 | } 87 | 88 | /// A convenience method to the contents of the input with [id]. 89 | /// 90 | /// This is equivalent to calling [getInput] followed by [Asset.read]. 91 | /// 92 | /// If the asset was created from a [String], this returns its UTF-8 encoding. 93 | /// 94 | /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 95 | Stream> readInput(AssetId id) => 96 | futureStream(getInput(id).then((input) => input.read())); 97 | 98 | /// A convenience method to return whether or not an asset exists. 99 | /// 100 | /// This is equivalent to calling [getInput] and catching an 101 | /// [AssetNotFoundException]. 102 | Future hasInput(AssetId id) { 103 | return DelegatingFuture 104 | .typed(getInput(id).then((_) => true).catchError((error) { 105 | if (error is AssetNotFoundException && error.id == id) return false; 106 | throw error; 107 | })); 108 | } 109 | 110 | /// Stores [output] as an output created by this transformation. 111 | /// 112 | /// A transformation can output as many assets as it wants. 113 | void addOutput(Asset output) { 114 | // TODO(rnystrom): This should immediately throw if an output with that ID 115 | // has already been created by this transformer. 116 | _outputs.add(output); 117 | } 118 | 119 | void consumePrimary(AssetId id) { 120 | if (!_emittedPrimaryInputs.containsId(id)) { 121 | throw new StateError( 122 | "$id can't be consumed because it's not a primary input."); 123 | } 124 | 125 | super.consumePrimary(id); 126 | } 127 | } 128 | 129 | /// The controller for [AggregateTransform]. 130 | class AggregateTransformController extends BaseTransformController { 131 | final AggregateTransform transform; 132 | 133 | /// The set of assets that the transformer has emitted. 134 | AssetSet get outputs => transform._outputs; 135 | 136 | bool get isDone => transform._inputController.isClosed; 137 | 138 | AggregateTransformController(TransformNode node) 139 | : transform = new AggregateTransform._(node); 140 | 141 | /// Adds a primary input asset to the [AggregateTransform.primaryInputs] 142 | /// stream. 143 | void addInput(Asset input) { 144 | transform._emittedPrimaryInputs.add(input); 145 | transform._inputController.add(input); 146 | } 147 | 148 | /// Returns whether an input with the given [id] was added via [addInput]. 149 | bool addedId(AssetId id) => transform._emittedPrimaryInputs.containsId(id); 150 | 151 | void done() { 152 | transform._inputController.close(); 153 | } 154 | } 155 | --------------------------------------------------------------------------------