Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(template_cache_generator): simple template cache generator
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelgj committed Dec 19, 2013
1 parent 1793b93 commit 32e073b
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bin/template_cache_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:angular/tools/template_cache_generator.dart' as generator;

main(args) {
generator.main(args);
}
21 changes: 21 additions & 0 deletions lib/tools/template_cache_annotation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
library angular.template_cache_annotation;

/**
* Annotation which will control the caching behavior of objects in the
* template_cache.
*
* Primary use cases are:
* - Adding URLs to the cache which cannot be automatically gathered from
* NgComponent annotations.
* - Adding annotation to an NgComponent to remove it from the template cache.
*/
class NgTemplateCache {
// List of strings to add to the template cache.
final List<String> preCacheUrls;
// Whether to cache these resources in the template cache. Primary use is to
// override the default caching behavior for NgComponent annotation.
final bool cache;

const NgTemplateCache(
{this.preCacheUrls : const <String> [], this.cache : true});
}
201 changes: 201 additions & 0 deletions lib/tools/template_cache_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
library angular.template_cache_generator;

import 'dart:io';
import 'dart:async';
import 'dart:collection';

import 'package:analyzer/src/generated/ast.dart';
import 'source_crawler_impl.dart';

const String PACKAGE_PREFIX = 'package:';
const String DART_PACKAGE_PREFIX = 'dart:';

String fileHeader(String library) => '''// GENERATED, DO NOT EDIT!
library ${library};
import 'package:angular/angular.dart';
primeTemplateCache(TemplateCache tc) {
''';

const String FILE_FOOTER = '}';
const SYSTEM_PACKAGE_ROOT = '%SYSTEM_PACKAGE_ROOT%';

main(args) {
if (args.length < 4) {
print('Usage: templace_cache_generator path_to_entry_point output '
'package_root1,package_root2,...|$SYSTEM_PACKAGE_ROOT '
'patternUrl1,rewriteTo1;patternUrl2,rewriteTo2 '
'blacklistClass1,blacklistClass2');
exit(1);
}

var entryPoint = args[0];
var output = args[1];
var outputLibrary = args[2];
var packageRoots = args[3] == SYSTEM_PACKAGE_ROOT ?
[Platform.packageRoot] : args[3].split(',');
Map<RegExp, String> urlRewriters = parseUrlRemapping(args[4]);
Set<String> blacklistedClasses = (args.length > 5)
? new Set.from(args[5].split(','))
: new Set();

print('entryPoint: $entryPoint');
print('output: $output');
print('outputLibrary: $outputLibrary');
print('packageRoots: $packageRoots');
print('url rewritters: ' + args[4]);
print('blacklistedClasses: ' + blacklistedClasses.join(', '));


Map<String, String> templates = {};

var c = new SourceCrawlerImpl(packageRoots);
var visitor =
new TemplateCollectingVisitor(templates, blacklistedClasses, c);
c.crawl(entryPoint, (CompilationUnit compilationUnit) =>
visitor(compilationUnit));

var sink = new File(output).openWrite();
return printTemplateCache(
templates, urlRewriters, outputLibrary, sink).then((_) {
return sink.flush();
});
}

Map<RegExp, String> parseUrlRemapping(String argument) {
Map<RegExp, String> result = new LinkedHashMap();
if (argument.isEmpty) {
return result;
}

argument.split(";").forEach((String pair) {
List<String> remapping = pair.split(",");
result[new RegExp(remapping[0])] = remapping[1];
});
return result;
}

printTemplateCache(Map<String, String> templateKeyMap,
Map<RegExp, String> urlRewriters,
String outputLibrary,
IOSink outSink) {

outSink.write(fileHeader(outputLibrary));

List<Future> reads = <Future>[];
templateKeyMap.forEach((uri, templateFile) {
reads.add(new File(templateFile).readAsString().then((fileStr) {
fileStr = fileStr.replaceAll('"""', r'\"\"\"');
String resultUri = uri;
urlRewriters.forEach((regexp, replacement) {
resultUri = resultUri.replaceFirst(regexp, replacement);
});
outSink.write(
'tc.put("$resultUri", new HttpResponse(200, r"""$fileStr"""));\n');
}));
});

// Wait until all templates files are processed.
return Future.wait(reads).then((_) {
outSink.write(FILE_FOOTER);
});
}

class TemplateCollectingVisitor {
Map<String, String> templates;
Set<String> blacklistedClasses;
SourceCrawlerImpl sourceCrawlerImpl;

TemplateCollectingVisitor(this.templates, this.blacklistedClasses,
this.sourceCrawlerImpl);

call(CompilationUnit cu) {
cu.declarations.forEach((CompilationUnitMember declaration) {
// We only care about classes.
if (declaration is! ClassDeclaration) return;
ClassDeclaration clazz = declaration;
List<String> cacheUris = [];
bool cache = true;
clazz.metadata.forEach((Annotation ann) {
if (ann.arguments == null) return; // Ignore non-class annotations.
// TODO(tsander): Add library name as class name could conflict.
if (blacklistedClasses.contains(clazz.name.name)) return;

switch (ann.name.name) {
case 'NgComponent':
extractNgComponentMetadata(ann, cacheUris); break;
case 'NgTemplateCache':
cache = extractNgTemplateCache(ann, cacheUris); break;
}
});
if (cache && cacheUris.isNotEmpty) {
cacheUris.forEach((uri) => storeUriAsset(uri));
}
});
}

void extractNgComponentMetadata(Annotation ann, List<String> cacheUris) {
ann.arguments.arguments.forEach((Expression arg) {
if (arg is NamedExpression) {
NamedExpression namedArg = arg;
var paramName = namedArg.name.label.name;
if (paramName == 'templateUrl' || paramName == 'cssUrl') {
cacheUris.add(assertString(namedArg.expression).stringValue);
}
}
});
}

bool extractNgTemplateCache(
Annotation ann, List<String> cacheUris) {
bool cache = true;
ann.arguments.arguments.forEach((Expression arg) {
if (arg is NamedExpression) {
NamedExpression namedArg = arg;
var paramName = namedArg.name.label.name;
if (paramName == 'preCacheUrls') {
assertList(namedArg.expression).elements
.forEach((expression) =>
cacheUris.add(assertString(expression).stringValue));
}
if (paramName == 'cache') {
cache = assertBoolean(namedArg.expression).value;
}
}
});
return cache;
}

void storeUriAsset(String uri) {
String assetFileLocation =
uri.startsWith('package:') ?
sourceCrawlerImpl.resolvePackagePath(uri) : uri;
if (assetFileLocation == null) {
print("Could not find asset for uri: $uri");
} else {
templates[uri] = assetFileLocation;
}
}

BooleanLiteral assertBoolean(Expression key) {
if (key is! BooleanLiteral) {
throw 'must be a boolean literal: ${key.runtimeType}';
}
return key;
}

ListLiteral assertList(Expression key) {
if (key is! ListLiteral) {
throw 'must be a list literal: ${key.runtimeType}';
}
return key;
}

StringLiteral assertString(Expression key) {
if (key is! StringLiteral) {
throw 'must be a string literal: ${key.runtimeType}';
}
return key;
}
}
2 changes: 2 additions & 0 deletions test/io/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ library all_io_tests;

import 'source_metadata_extractor_spec.dart' as source_metadata_extractor_spec;
import 'expression_extractor_spec.dart' as expression_extractor_spec;
import 'template_cache_generator_spec.dart' as template_cache_generator_spec;

main() {
source_metadata_extractor_spec.main();
expression_extractor_spec.main();
template_cache_generator_spec.main();
}
36 changes: 36 additions & 0 deletions test/io/template_cache_generator_spec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
library ng.tool.template_cache_generator_spec;

import 'dart:async';
import 'dart:io';

import 'package:angular/tools/template_cache_generator.dart' as generator;
import '../jasmine_syntax.dart';
import 'package:unittest/unittest.dart';

main() => describe('template_cache_generator', () {
it('should correctly generate the templates cache file', () {
var tmpDir = Directory.systemTemp.createTempSync();
Future flush;
try {
flush = generator.main(['test/io/test_files/templates/main.dart',
'${tmpDir.path}/generated.dart', 'generated', '%SYSTEM_PACKAGE_ROOT%',
'test/io/test_files,rewritten', 'MyComponent3']);
} catch(_) {
tmpDir.deleteSync(recursive: true);
rethrow;
}
return flush.then((_) {
expect(new File('${tmpDir.path}/generated.dart').readAsStringSync(),
'// GENERATED, DO NOT EDIT!\n'
'library generated;\n'
'\n'
'import \'package:angular/angular.dart\';\n'
'\n'
'primeTemplateCache(TemplateCache tc) {\n'
'tc.put("rewritten/templates/main.html", new HttpResponse(200, r"""Hello World!"""));\n'
'}');
}).whenComplete(() {
tmpDir.deleteSync(recursive: true);
});
});
});
1 change: 1 addition & 0 deletions test/io/test_files/templates/dont.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
If you find me in the templates cache then it's broken. :(
29 changes: 29 additions & 0 deletions test/io/test_files/templates/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
library test_files.main;

import 'package:angular/core/module.dart';
import 'package:angular/tools/template_cache_annotation.dart';

@NgComponent(
selector: 'my-component',
templateUrl: 'test/io/test_files/templates/main.html'
)
@NgTemplateCache()
class MyComponent {
}

@NgComponent(
selector: 'my-component2',
templateUrl: 'test/io/test_files/templates/dont.html'
)
@NgTemplateCache(cache: false)
class MyComponent2 {
}


@NgComponent(
selector: 'my-component3',
templateUrl: 'test/io/test_files/templates/dont.html'
)
@NgTemplateCache(cache: true)
class MyComponent3 {
}
1 change: 1 addition & 0 deletions test/io/test_files/templates/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello World!

0 comments on commit 32e073b

Please sign in to comment.