diff --git a/angel_jael/LICENSE b/angel_jael/LICENSE
new file mode 100644
index 00000000..89074fd3
--- /dev/null
+++ b/angel_jael/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 The Angel Framework
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/jael.iml b/jael.iml
index d1021572..697b81c1 100644
--- a/jael.iml
+++ b/jael.iml
@@ -7,8 +7,12 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/jael/LICENSE b/jael/LICENSE
new file mode 100644
index 00000000..89074fd3
--- /dev/null
+++ b/jael/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 The Angel Framework
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/jael/lib/src/ast/element.dart b/jael/lib/src/ast/element.dart
index 7e184a2a..f9200db5 100644
--- a/jael/lib/src/ast/element.dart
+++ b/jael/lib/src/ast/element.dart
@@ -17,6 +17,7 @@ class TextNode extends ElementChild {
abstract class Element extends ElementChild {
static const List selfClosing = const [
+ 'include',
'base',
'basefont',
'frame',
diff --git a/jael/pubspec.yaml b/jael/pubspec.yaml
index 4caf4f02..cacb7187 100644
--- a/jael/pubspec.yaml
+++ b/jael/pubspec.yaml
@@ -1,8 +1,8 @@
name: jael
-version: 1.0.0-alpha
+version: 1.0.0-alpha+1
description: A simple server-side HTML templating engine for Dart.
author: Tobe O
-homepage:
+homepage: https://github.com/angel-dart/jael/tree/master/jael
environment:
sdk: ">=1.19.0"
dependencies:
diff --git a/jael_preprocessor/LICENSE b/jael_preprocessor/LICENSE
new file mode 100644
index 00000000..89074fd3
--- /dev/null
+++ b/jael_preprocessor/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 The Angel Framework
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/jael_preprocessor/lib/jael_preprocessor.dart b/jael_preprocessor/lib/jael_preprocessor.dart
new file mode 100644
index 00000000..0296b8f0
--- /dev/null
+++ b/jael_preprocessor/lib/jael_preprocessor.dart
@@ -0,0 +1,201 @@
+import 'dart:async';
+import 'package:file/file.dart';
+import 'package:jael/jael.dart';
+
+/// Expands all `block[name]` tags within the template, replacing them with the correct content.
+Future resolve(Document document, Directory currentDirectory,
+ {void onError(JaelError error)}) async {
+ onError ?? (e) => throw e;
+
+ // Resolve all includes...
+ var includesResolved =
+ await resolveIncludes(document, currentDirectory, onError);
+
+ if (includesResolved.root.tagName.name != 'extend') return includesResolved;
+
+ var element = includesResolved.root;
+ var attr = element.attributes
+ .firstWhere((a) => a.name.name == 'src', orElse: () => null);
+ if (attr == null) {
+ onError(new JaelError(JaelErrorSeverity.warning,
+ 'Missing "src" attribute in "extend" tag.', element.tagName.span));
+ return null;
+ } else if (attr.value is! StringLiteral) {
+ onError(new JaelError(
+ JaelErrorSeverity.warning,
+ 'The "src" attribute in an "extend" tag must be a string literal.',
+ element.tagName.span));
+ return null;
+ } else {
+ var src = (attr.value as StringLiteral).value;
+ var file =
+ currentDirectory.fileSystem.file(currentDirectory.uri.resolve(src));
+ var contents = await file.readAsString();
+ var doc = parseDocument(contents, sourceUrl: file.uri, onError: onError);
+ var processed = await resolve(
+ doc, currentDirectory.fileSystem.directory(file.dirname),
+ onError: onError);
+
+ Map blocks = {};
+ var blockElements = element.children
+ .where((e) => e is Element && e.tagName.name == 'block');
+
+ for (Element blockElement in blockElements) {
+ var nameAttr = blockElement.attributes
+ .firstWhere((a) => a.name.name == 'name', orElse: () => null);
+ if (nameAttr == null) {
+ onError(new JaelError(JaelErrorSeverity.warning,
+ 'Missing "name" attribute in "block" tag.', blockElement.span));
+ } else if (nameAttr.value is! StringLiteral) {
+ onError(new JaelError(
+ JaelErrorSeverity.warning,
+ 'The "name" attribute in an "block" tag must be a string literal.',
+ nameAttr.span));
+ } else {
+ var name = (nameAttr.value as StringLiteral).value;
+ blocks[name] = blockElement;
+ }
+ }
+
+ var blocksExpanded =
+ await _expandBlocks(processed.root, blocks, currentDirectory, onError);
+ return new Document(document.doctype ?? processed.doctype, blocksExpanded);
+ }
+}
+
+Future _expandBlocks(Element element, Map blocks,
+ Directory currentDirectory, void onError(JaelError error)) async {
+ if (element is SelfClosingElement)
+ return element;
+ else if (element is RegularElement) {
+ if (element.children.isEmpty) return element;
+
+ List expanded = [];
+
+ for (var child in element.children) {
+ if (child is Element) {
+ if (child is SelfClosingElement)
+ expanded.add(child);
+ else if (child is RegularElement) {
+ if (child.tagName.name != 'block') {
+ expanded.add(child);
+ } else {
+ var nameAttr =
+ child.attributes.firstWhere((a) => a.name.name == 'name', orElse: () => null);
+ if (nameAttr == null) {
+ onError(new JaelError(
+ JaelErrorSeverity.warning,
+ 'Missing "name" attribute in "block" tag.',
+ child.span));
+ } else if (nameAttr.value is! StringLiteral) {
+ onError(new JaelError(
+ JaelErrorSeverity.warning,
+ 'The "name" attribute in an "block" tag must be a string literal.',
+ nameAttr.span));
+ }
+
+ var name = (nameAttr.value as StringLiteral).value;
+ Iterable children;
+
+ if (!blocks.containsKey(name)) {
+ children = child.children;
+ } else {
+ children = blocks[name].children;
+ }
+
+ expanded.addAll(children);
+ }
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported element type: ${element.runtimeType}');
+ }
+ } else {
+ expanded.add(child);
+ }
+ }
+
+ // Resolve all includes...
+ expanded = await Future.wait(expanded.map((c) {
+ if (c is! Element) return new Future.value(c);
+ return _expandIncludes(c, currentDirectory, onError);
+ }));
+
+ return new RegularElement(
+ element.lt,
+ element.tagName,
+ element.attributes,
+ element.gt,
+ expanded,
+ element.lt2,
+ element.slash,
+ element.tagName2,
+ element.gt2);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported element type: ${element.runtimeType}');
+ }
+}
+
+/// Expands all `include[src]` tags within the template, and fills in the content of referenced files.
+Future resolveIncludes(Document document, Directory currentDirectory,
+ void onError(JaelError error)) async {
+ return new Document(document.doctype,
+ await _expandIncludes(document.root, currentDirectory, onError));
+}
+
+Future _expandIncludes(Element element, Directory currentDirectory,
+ void onError(JaelError error)) async {
+ if (element.tagName.name != 'include') {
+ if (element is SelfClosingElement)
+ return element;
+ else if (element is RegularElement) {
+ List expanded = [];
+
+ for (var child in element.children) {
+ if (child is Element) {
+ expanded.add(await _expandIncludes(child, currentDirectory, onError));
+ } else {
+ expanded.add(child);
+ }
+ }
+
+ return new RegularElement(
+ element.lt,
+ element.tagName,
+ element.attributes,
+ element.gt,
+ expanded,
+ element.lt2,
+ element.slash,
+ element.tagName2,
+ element.gt2);
+ } else {
+ throw new UnsupportedError(
+ 'Unsupported element type: ${element.runtimeType}');
+ }
+ }
+
+ var attr = element.attributes
+ .firstWhere((a) => a.name.name == 'src', orElse: () => null);
+ if (attr == null) {
+ onError(new JaelError(JaelErrorSeverity.warning,
+ 'Missing "src" attribute in "include" tag.', element.tagName.span));
+ return null;
+ } else if (attr.value is! StringLiteral) {
+ onError(new JaelError(
+ JaelErrorSeverity.warning,
+ 'The "src" attribute in an "include" tag must be a string literal.',
+ element.tagName.span));
+ return null;
+ } else {
+ var src = (attr.value as StringLiteral).value;
+ var file =
+ currentDirectory.fileSystem.file(currentDirectory.uri.resolve(src));
+ var contents = await file.readAsString();
+ var doc = parseDocument(contents, sourceUrl: file.uri, onError: onError);
+ var processed = await resolve(
+ doc, currentDirectory.fileSystem.directory(file.dirname),
+ onError: onError);
+ return processed.root;
+ }
+}
diff --git a/jael_preprocessor/pubspec.yaml b/jael_preprocessor/pubspec.yaml
index 5c607e9a..6d63db9e 100644
--- a/jael_preprocessor/pubspec.yaml
+++ b/jael_preprocessor/pubspec.yaml
@@ -1,4 +1,10 @@
name: jael_preprocessor
+version: 1.0.0-alpha
+description: A pre-processor for resolving blocks and includes within Jael templates.
+author: Tobe O
+homepage: https://github.com/angel-dart/jael/tree/master/jael_processor
+environment:
+ sdk: ">=1.19.0"
dependencies:
file: ^2.0.0
jael: ^1.0.0-alpha
diff --git a/jael_preprocessor/test/block_test.dart b/jael_preprocessor/test/block_test.dart
new file mode 100644
index 00000000..e5d83b97
--- /dev/null
+++ b/jael_preprocessor/test/block_test.dart
@@ -0,0 +1,74 @@
+import 'package:code_buffer/code_buffer.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:jael/jael.dart' as jael;
+import 'package:jael_preprocessor/jael_preprocessor.dart' as jael;
+import 'package:symbol_table/symbol_table.dart';
+import 'package:test/test.dart';
+
+main() {
+ FileSystem fileSystem;
+
+ setUp(() {
+ fileSystem = new MemoryFileSystem();
+
+ // a.jl
+ fileSystem.file('a.jl').writeAsStringSync('a.jl');
+
+ // b.jl
+ fileSystem.file('b.jl').writeAsStringSync(
+ 'Hello
');
+
+ // c.jl
+ fileSystem.file('c.jl').writeAsStringSync(
+ 'Goodbye');
+
+ // d.jl
+ fileSystem.file('d.jl').writeAsStringSync(
+ 'Saluton!');
+ });
+
+ test('blocks are replaced or kept', () async {
+ var file = fileSystem.file('c.jl');
+ var original = jael.parseDocument(await file.readAsString(),
+ sourceUrl: file.uri, onError: (e) => throw e);
+ var processed = await jael.resolve(
+ original, fileSystem.directory(fileSystem.currentDirectory),
+ onError: (e) => throw e);
+ var buf = new CodeBuffer();
+ var scope = new SymbolTable();
+ const jael.Renderer().render(processed, buf, scope);
+ print(buf);
+
+ expect(buf.toString(), '''
+
+
+ a.jl
+
+ Goodbye
+
+ '''.trim());
+ });
+
+ test('blocks can be overwritten', () async {
+ var file = fileSystem.file('d.jl');
+ var original = jael.parseDocument(await file.readAsString(),
+ sourceUrl: file.uri, onError: (e) => throw e);
+ var processed = await jael.resolve(
+ original, fileSystem.directory(fileSystem.currentDirectory),
+ onError: (e) => throw e);
+ var buf = new CodeBuffer();
+ var scope = new SymbolTable();
+ const jael.Renderer().render(processed, buf, scope);
+ print(buf);
+
+ expect(buf.toString(), '''
+
+
+ a.jl
+
+ Saluton!
+
+ '''.trim());
+ });
+}
diff --git a/jael_preprocessor/test/include_test.dart b/jael_preprocessor/test/include_test.dart
new file mode 100644
index 00000000..4b23bb11
--- /dev/null
+++ b/jael_preprocessor/test/include_test.dart
@@ -0,0 +1,49 @@
+import 'package:code_buffer/code_buffer.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:jael/jael.dart' as jael;
+import 'package:jael_preprocessor/jael_preprocessor.dart' as jael;
+import 'package:symbol_table/symbol_table.dart';
+import 'package:test/test.dart';
+
+main() {
+ FileSystem fileSystem;
+
+ setUp(() {
+ fileSystem = new MemoryFileSystem();
+
+ // a.jl
+ fileSystem.file('a.jl').writeAsStringSync('a.jl');
+
+ // b.jl
+ fileSystem.file('b.jl').writeAsStringSync('');
+
+ // c.jl
+ fileSystem.file('c.jl').writeAsStringSync('');
+ });
+
+ test('includes are expanded', () async {
+ var file = fileSystem.file('c.jl');
+ var original = jael.parseDocument(await file.readAsString(),
+ sourceUrl: file.uri, onError: (e) => throw e);
+ var processed = await jael.resolveIncludes(original,
+ fileSystem.directory(fileSystem.currentDirectory), (e) => throw e);
+ var buf = new CodeBuffer();
+ var scope = new SymbolTable();
+ const jael.Renderer().render(processed, buf, scope);
+ print(buf);
+
+ expect(
+ buf.toString(),
+ '''
+
+
+
+ a.jl
+
+
+
+'''
+ .trim());
+ });
+}
diff --git a/travis.sh b/travis.sh
index 0b294d92..f8414112 100644
--- a/travis.sh
+++ b/travis.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
cd jael && pub get && pub run test
-cd ../angel_jael && pub get && pub run test
+cd ../jael_preprocessor/ && pub get && pub run test