diff --git a/CHANGELOG.md b/CHANGELOG.md index 729fb69..63780c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +## 0.4.0 + +- Allow to set a master template within a template. +```dart +@template +void _productTemplate(Product product) { + // ... the template + + // Call this to automatically wrap productTemplate in the pageTemplate + template.master = (body) => pageTemplate(title: product.name, body: body); +} + +@template +void _pageTemplate({required String title, required TrustedHtml body}) { + ''' + + $title + $body + + '''; +} + +``` + ## 0.3.1 - Upgrade dependencies diff --git a/example/lib/condition.g.dart b/example/lib/condition.g.dart index cb6f815..7a3f74c 100644 --- a/example/lib/condition.g.dart +++ b/example/lib/condition.g.dart @@ -7,8 +7,11 @@ part of 'condition.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_conditionExample) Future conditionExample({required bool someCondition}) async { var $ = StringBuffer(); @@ -43,8 +46,11 @@ Future conditionExample({required bool someCondition}) async { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_conditionAlt) TrustedHtml conditionAlt({required bool showMenu}) { var $ = StringBuffer(); diff --git a/example/lib/css_classes.g.dart b/example/lib/css_classes.g.dart index d089363..e68e020 100644 --- a/example/lib/css_classes.g.dart +++ b/example/lib/css_classes.g.dart @@ -7,8 +7,11 @@ part of 'css_classes.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_cssClassesExample) TrustedHtml cssClassesExample(List data, {bool showMenu = false}) { var $ = StringBuffer(); diff --git a/example/lib/loop.g.dart b/example/lib/loop.g.dart index 17c507b..cf14dc1 100644 --- a/example/lib/loop.g.dart +++ b/example/lib/loop.g.dart @@ -7,8 +7,11 @@ part of 'loop.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_simpleLoop) TrustedHtml simpleLoop(List menu) { var $ = StringBuffer(); @@ -32,8 +35,11 @@ TrustedHtml simpleLoop(List menu) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_alternativeLoop) TrustedHtml alternativeLoop(List menu) { var $ = StringBuffer(); diff --git a/example/lib/main.g.dart b/example/lib/main.g.dart index 27ef1c6..bd05d1c 100644 --- a/example/lib/main.g.dart +++ b/example/lib/main.g.dart @@ -7,8 +7,11 @@ part of 'main.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_productTemplate) TrustedHtml productTemplate(Product product) { var $ = StringBuffer(); @@ -27,8 +30,11 @@ TrustedHtml productTemplate(Product product) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_pageTemplate) TrustedHtml pageTemplate(Product product, {List? scripts}) { var $ = StringBuffer(); diff --git a/example/lib/master_details.dart b/example/lib/master_details.dart new file mode 100644 index 0000000..649c358 --- /dev/null +++ b/example/lib/master_details.dart @@ -0,0 +1,36 @@ +import 'package:html_template/html_template.dart'; + +part 'master_details.g.dart'; + +// ignore_for_file: unnecessary_statements + +@template +void _productTemplate(Product product) { + ''' + +

$product

+ '''; + + template.master = (body) => master(title: product.name, body: body); +} + +@template +void _master({required String title, required TrustedHtml body}) { + ''; + ''' + + + $title + + + $body + + + '''; +} + +class Product { + bool get isNew => false; + String? get icon => ''; + String get name => ''; +} diff --git a/example/lib/master_details.g.dart b/example/lib/master_details.g.dart new file mode 100644 index 0000000..b285a7b --- /dev/null +++ b/example/lib/master_details.g.dart @@ -0,0 +1,66 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'master_details.dart'; + +// ************************************************************************** +// TemplateGenerator +// ************************************************************************** + +// ignore_for_file: duplicate_ignore +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps +// ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable +@GenerateFor(_productTemplate) +TrustedHtml productTemplate(Product product) { + var $ = StringBuffer(); + + $.write(' '); + if (template.nonNullBool(product.icon != null)) { + $.write(''); + } + $.write('\n '); + $.write(''); + $.write('${TrustedHtml.escape(product)}'); + $.write(''); + $.write('\n '); + + TrustedHtml Function(TrustedHtml) $master = + (body) => master(title: product.name, body: body); + + return $master(TrustedHtml($.toString())); +} + +// ignore_for_file: duplicate_ignore +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps +// ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable +@GenerateFor(_master) +TrustedHtml master({required String title, required TrustedHtml body}) { + var $ = StringBuffer(); + + $.writeln(''); + + $.write(''); + $.write(''); + $.write('\n '); + $.write(''); + $.write('${TrustedHtml.escape(title)}'); + $.write(''); + $.write('\n '); + $.write(''); + $.write('\n '); + $.write(''); + $.write(''' + ${TrustedHtml.escape(body)} + + + '''); + $.write(''); + $.write(''); + + return TrustedHtml($.toString()); +} diff --git a/example/lib/multiple_literals.g.dart b/example/lib/multiple_literals.g.dart index 44fd66c..ef81723 100644 --- a/example/lib/multiple_literals.g.dart +++ b/example/lib/multiple_literals.g.dart @@ -7,8 +7,11 @@ part of 'multiple_literals.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_movieTemplate) Future movieTemplate() async { var $ = StringBuffer(); diff --git a/example/lib/nested.g.dart b/example/lib/nested.g.dart index 7f733cb..2dcf548 100644 --- a/example/lib/nested.g.dart +++ b/example/lib/nested.g.dart @@ -7,8 +7,11 @@ part of 'nested.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_myTemplate) TrustedHtml myTemplate() { var $ = StringBuffer(); @@ -25,8 +28,11 @@ TrustedHtml myTemplate() { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_img) TrustedHtml img(String url) { var $ = StringBuffer(); diff --git a/example/lib/switch.g.dart b/example/lib/switch.g.dart index c48e6df..92d9b07 100644 --- a/example/lib/switch.g.dart +++ b/example/lib/switch.g.dart @@ -7,8 +7,11 @@ part of 'switch.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_switchExample) TrustedHtml switchExample(Season season) { var $ = StringBuffer(); diff --git a/example/pubspec.lock b/example/pubspec.lock index 033d32b..eeccf8d 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -223,7 +223,7 @@ packages: path: ".." relative: true source: path - version: "0.3.1" + version: "0.4.0" http: dependency: transitive description: diff --git a/example/test/template_test.g.dart b/example/test/template_test.g.dart index 46f9fed..41ed961 100644 --- a/example/test/template_test.g.dart +++ b/example/test/template_test.g.dart @@ -7,8 +7,11 @@ part of 'template_test.dart'; // ************************************************************************** // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_emptyTemplate) TrustedHtml emptyTemplate() { var $ = StringBuffer(); @@ -17,8 +20,11 @@ TrustedHtml emptyTemplate() { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_simpleTemplate) TrustedHtml simpleTemplate() { var $ = StringBuffer(); @@ -29,8 +35,11 @@ TrustedHtml simpleTemplate() { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_condition) TrustedHtml condition({bool? myVar = false}) { var $ = StringBuffer(); @@ -43,8 +52,11 @@ TrustedHtml condition({bool? myVar = false}) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_interpolation) TrustedHtml interpolation(String? someText) { var $ = StringBuffer(); @@ -55,8 +67,11 @@ TrustedHtml interpolation(String? someText) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_loop) TrustedHtml loop(Iterable? list) { var $ = StringBuffer(); @@ -72,8 +87,11 @@ TrustedHtml loop(Iterable? list) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_switchTemplate) TrustedHtml switchTemplate(MyEnum? myEnum) { var $ = StringBuffer(); @@ -105,8 +123,11 @@ TrustedHtml switchTemplate(MyEnum? myEnum) { } // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable @GenerateFor(_attribute) TrustedHtml attribute({ bool disabled = false, diff --git a/lib/src/annotation.dart b/lib/src/annotation.dart index a8d7056..69a9193 100644 --- a/lib/src/annotation.dart +++ b/lib/src/annotation.dart @@ -46,6 +46,9 @@ class TemplateAnnotation { Iterable nonNullIterable(Iterable? iterable) => iterable ?? const []; bool nonNullBool(bool? value) => value ?? false; + + // ignore: avoid_setters_without_getters + set master(void Function(TrustedHtml body) template) {} } /// The annotation to put on a private method to activate the builder on it. diff --git a/lib/src/code_generator.dart b/lib/src/code_generator.dart index 5a61acd..b20ca6f 100644 --- a/lib/src/code_generator.dart +++ b/lib/src/code_generator.dart @@ -72,6 +72,7 @@ String generateCodeFromFunction( code.writeln(''); var body = function.functionExpression.body; + var hasMaster = false; if (body is BlockFunctionBody) { var visitor = _Visitor(options); body.visitChildren(visitor); @@ -79,6 +80,7 @@ String generateCodeFromFunction( var bodyCode = StringBuffer(); var printer = _Printer(bodyCode, visitor._replacements); body.block.visitChildren(printer); + hasMaster = printer.hasMaster; code.writeln(bodyCode); } else if (body is ExpressionFunctionBody) { @@ -89,7 +91,12 @@ String generateCodeFromFunction( } code.writeln(''); - code.writeln(r'return TrustedHtml($.toString());'); + var bodyExpression = r'TrustedHtml($.toString())'; + if (hasMaster) { + bodyExpression = '\$master($bodyExpression)'; + } + + code.writeln('return $bodyExpression;'); code.write('}'); return code.toString(); @@ -123,6 +130,7 @@ class _Replacement { class _Printer extends ToSourceVisitor { final List<_Replacement> replacements; + bool hasMaster = false; _Printer(super.sink, this.replacements); @@ -135,6 +143,21 @@ class _Printer extends ToSourceVisitor { super.visitExpressionStatement(node); } } + + @override + void visitAssignmentExpression(AssignmentExpression node) { + if (node.leftHandSide case PrefixedIdentifier prefixed) { + if (prefixed.prefix.name == 'template' && + prefixed.identifier.name == 'master') { + sink.writeln( + 'TrustedHtml Function(TrustedHtml) \$master = ${node.rightHandSide.toSource()}', + ); + hasMaster = true; + return; + } + } + super.visitAssignmentExpression(node); + } } String _handleStringLiteral(Options options, StringLiteral literal) { diff --git a/lib/src/generator.dart b/lib/src/generator.dart index 00af16d..af4b42f 100644 --- a/lib/src/generator.dart +++ b/lib/src/generator.dart @@ -31,8 +31,11 @@ class TemplateGenerator extends GeneratorForAnnotation { var code = generateCodeFromFunction(functionDeclaration); code = ''' // ignore_for_file: duplicate_ignore -// ignore_for_file: unused_local_variable +// ignore_for_file: omit_local_variable_types +// ignore_for_file: prefer_function_declarations_over_variables +// ignore_for_file: unnecessary_brace_in_string_interps // ignore_for_file: unnecessary_string_interpolations +// ignore_for_file: unused_local_variable $code '''; return code; diff --git a/pubspec.yaml b/pubspec.yaml index b3c2912..5a2b380 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: html_template description: A server-side HTML template engine based on String interpolation. -version: 0.3.1 +version: 0.4.0 homepage: https://github.com/xvrh/html_template environment: diff --git a/test/generator_test.dart b/test/generator_test.dart index 4676c96..ce7a684 100644 --- a/test/generator_test.dart +++ b/test/generator_test.dart @@ -970,4 +970,40 @@ TrustedHtml html() { throwsA(TypeMatcher()), ); }); + + test('Convert master template', () { + var input = r'''void _myTemplate() { + template.master = (body) => myMaster(body); + }'''; + expect( + generateCodeForTest(input), + startsWith(r'''TrustedHtml myTemplate() { + var $ = StringBuffer(); + + TrustedHtml Function(TrustedHtml) $master = (body) => myMaster(body); + + return $master(TrustedHtml($.toString())); +} +'''), + ); + }); + + test('Convert master template with block', () { + var input = r'''void _myTemplate() { + template.master = (body) { myMaster(body); }; + }'''; + expect( + generateCodeForTest(input), + startsWith(r'''TrustedHtml myTemplate() { + var $ = StringBuffer(); + + TrustedHtml Function(TrustedHtml) $master = (body) { + myMaster(body); + }; + + return $master(TrustedHtml($.toString())); +} +'''), + ); + }); }