From 2056d1eacc87970c41dc655230ed7d47723e0848 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 30 Sep 2017 01:27:31 -0400 Subject: [PATCH] Pre-processor needs work --- angel_jael/LICENSE | 21 ++ jael.iml | 6 +- jael/LICENSE | 21 ++ jael/lib/src/ast/element.dart | 1 + jael/pubspec.yaml | 4 +- jael_preprocessor/LICENSE | 21 ++ jael_preprocessor/lib/jael_preprocessor.dart | 201 +++++++++++++++++++ jael_preprocessor/pubspec.yaml | 6 + jael_preprocessor/test/block_test.dart | 74 +++++++ jael_preprocessor/test/include_test.dart | 49 +++++ travis.sh | 2 +- 11 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 angel_jael/LICENSE create mode 100644 jael/LICENSE create mode 100644 jael_preprocessor/LICENSE create mode 100644 jael_preprocessor/lib/jael_preprocessor.dart create mode 100644 jael_preprocessor/test/block_test.dart create mode 100644 jael_preprocessor/test/include_test.dart 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