From a5ff5edcd8824fef49693889d9d70b76af89802d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 21 Jul 2017 20:02:48 -0400 Subject: [PATCH 1/3] Initial commit --- .gitignore | 12 ++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4d2a4d6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..89074fd3 --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 00000000..946fd9ed --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# html_builder +A plug-in that allows you to return html_builder AST's from request handlers, and have them sent as HTML automatically. From 57f212eecccde27f4f2c5772eba934415f239059 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 23 Jul 2017 11:50:37 -0400 Subject: [PATCH 2/3] 1.0.0 --- .gitignore | 44 ++++++++++++++++++++++++ .idea/vcs.xml | 6 ++++ .travis.yml | 1 + README.md | 83 ++++++++++++++++++++++++++++++++++++++++++++- lib/angel_html.dart | 35 +++++++++++++++++++ pubspec.yaml | 14 ++++++++ test/all_test.dart | 75 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 .idea/vcs.xml create mode 100644 .travis.yml create mode 100644 lib/angel_html.dart create mode 100644 pubspec.yaml create mode 100644 test/all_test.dart diff --git a/.gitignore b/.gitignore index 4d2a4d6d..99e7978e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,47 @@ pubspec.lock # Directory created by dartdoc # If you don't generate documentation locally you can remove this line. doc/api/ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/README.md b/README.md index 946fd9ed..2bb57ee8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,83 @@ -# html_builder +# html +[![Pub](https://img.shields.io/pub/v/angel_html.svg)](https://pub.dartlang.org/packages/angel_html) +[![build status](https://travis-ci.org/angel-dart/html.svg)](https://travis-ci.org/angel-dart/html) + A plug-in that allows you to return html_builder AST's from request handlers, and have them sent as HTML automatically. + +[`package:html_builder`](https://github.com/thosakwe/html_builder) is a simple virtual DOM library +(without diffing, you can find that +[here](https://github.com/thosakwe/html_builder_vdom)), with a handy Dart DSL that makes it easy to build HTML +AST's: + +```dart +import 'package:html_builder/elements.dart'; + +Node myDom = html(lang: 'en', c: [ + head(c: [ + meta(name: 'viewport', content: 'width=device-width, initial-scale=1'), + title(c: [ + text('html_builder example page') + ]), + ]), + body(c: [ + h1(c: [ + text('Hello world!'), + ]), + ]), +]); +``` + +This plug-in means that you can now `return` these AST's, and Angel will automatically send them to +clients. Ultimately, the implication is that you can use `html_builder` as a substitute for a +templating system within Dart. With [hot reloading](https://github.com/angel-dart/hot), you won't +even need to reload your server (as it should be). + +# Installation +In your `pubspec.yaml`: + +```yaml +dependencies: + angel_html: ^1.0.0 +``` + +# Usage +The `renderHtml` function does all the magic for you. + +```dart +configureServer(Angel app) async { + // Wire it up! + app.before.add(renderHtml()); + + // You can pass a custom StringRenderer if you need more control over the output. + app.before.add(renderHtml(renderer: new StringRenderer(html5: false))); + + app.get('/greet/:name', (RequestContext req) { + return html(lang: 'en', c: [ + head(c: [ + meta(name: 'viewport', content: 'width=device-width, initial-scale=1'), + title(c: [ + text('Greetings!') + ]), + ]), + body(c: [ + h1(c: [ + text('Hello, ${req.params['id']}!'), + ]), + ]), + ]); + }); +} +``` + +By default, `renderHtml` will ignore the client's `Accept` header. However, if you pass +`enforceAcceptHeader` as `true`, then a `406 Not Acceptable` error will be thrown if the +client doesn't accept `*/*` or `text/html`. + +```dart +configureServer(Angel app) async { + // Wire it up! + app.before.add(renderHtml(enforceAcceptHeader: true)); + + // ... +} +``` \ No newline at end of file diff --git a/lib/angel_html.dart b/lib/angel_html.dart new file mode 100644 index 00000000..99563629 --- /dev/null +++ b/lib/angel_html.dart @@ -0,0 +1,35 @@ +import 'dart:async'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:html_builder/html_builder.dart'; + +/// Returns a [RequestMiddleware] that allows you to return `html_builder` [Node]s as responses. +/// +/// You can provide a custom [renderer]. The default renders minified HTML5 pages. +/// +/// Set [enforceAcceptHeader] to `true` to throw a `406 Not Acceptable` if the client doesn't accept HTML responses. +RequestMiddleware renderHtml( + {StringRenderer renderer, bool enforceAcceptHeader}) { + renderer ??= new StringRenderer(pretty: false, html5: true); + + return (RequestContext req, ResponseContext res) { + var oldSerializer = res.serializer; + + res.serializer = (data) { + if (data is! Node) + return oldSerializer(data); + else { + if (enforceAcceptHeader == true && !req.accepts('text/html')) + throw new AngelHttpException.notAcceptable(); + + var content = renderer.render(data); + res + ..headers['content-type'] = 'text/html' + ..write(content) + ..end(); + return ''; + } + }; + + return new Future.value(true); + }; +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..7cfb322a --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,14 @@ +name: angel_html +version: 1.0.0 +description: Easily render html_builder AST's as responses in Angel. +author: Tobe O +homepage: https://github.com/angel-dart/html_builder +environment: + sdk: ">=1.19.0" +dependencies: + angel_framework: ^1.0.0 + html_builder: ^1.0.0 +dev_dependencies: + angel_test: ^1.0.0 + html: ^0.13.2 + test: ^0.12.0 \ No newline at end of file diff --git a/test/all_test.dart b/test/all_test.dart new file mode 100644 index 00000000..92795a83 --- /dev/null +++ b/test/all_test.dart @@ -0,0 +1,75 @@ +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_html/angel_html.dart'; +import 'package:angel_test/angel_test.dart'; +import 'package:html_builder/elements.dart'; +import 'package:html_builder/html_builder.dart'; +import 'package:test/test.dart'; + +main() { + Angel app; + TestClient client; + + setUp(() async { + app = new Angel(); + + app.before.add(renderHtml()); + + app.get('/html', () { + return html(c: [ + head(c: [ + title(c: [text('ok')]) + ]) + ]); + }); + + app + .chain(renderHtml( + enforceAcceptHeader: true, + renderer: new StringRenderer(doctype: null, pretty: false))) + .get('/strict', () { + return div(c: [text('strict')]); + }); + + client = await connectTo(app); + }); + + tearDown(() => client.close()); + + test('sets content type and body', () async { + var response = await client.get('/html'); + print('Response: ${response.body}'); + + expect( + response, + allOf(hasContentType('text/html'), + hasBody('ok'))); + }); + + group('enforce accept header', () { + test('sends if correct accept or wildcard', () async { + var response = await client.get('/strict', headers: {'accept': '*/*'}); + print('Response: ${response.body}'); + expect(response, + allOf(hasContentType('text/html'), hasBody('
strict
'))); + + response = await client.get('/strict', + headers: {'accept': 'text/html,application/json,text/xml'}); + print('Response: ${response.body}'); + expect(response, + allOf(hasContentType('text/html'), hasBody('
strict
'))); + }); + + test('throws if incorrect or no accept', () async { + var response = await client.get('/strict'); + print('Response: ${response.body}'); + expect(response, + isAngelHttpException(statusCode: 406, message: '406 Not Acceptable')); + + response = await client + .get('/strict', headers: {'accept': 'application/json,text/xml'}); + print('Response: ${response.body}'); + expect(response, + isAngelHttpException(statusCode: 406, message: '406 Not Acceptable')); + }); + }); +} From 62d29b45fde6a235a24b280c461d35bf1158585e Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 6 Jan 2019 13:11:11 -0500 Subject: [PATCH 3/3] 2.0.0 --- .gitignore | 2 ++ CHANGELOG.md | 2 ++ README.md | 6 +++--- analysis_options.yaml | 4 ++++ example/main.dart | 46 +++++++++++++++++++++++++++++++++++++++++++ lib/angel_html.dart | 9 ++++----- pubspec.yaml | 14 +++++++------ test/all_test.dart | 36 ++++++++++++++++++++------------- 8 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 analysis_options.yaml create mode 100644 example/main.dart diff --git a/.gitignore b/.gitignore index 99e7978e..fe4c4cfa 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,5 @@ com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties + +.dart_tool \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..324effd8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 2.0.0 +* Angel 2 + Dart 2 updates. \ No newline at end of file diff --git a/README.md b/README.md index 2bb57ee8..cdf92e78 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ The `renderHtml` function does all the magic for you. ```dart configureServer(Angel app) async { // Wire it up! - app.before.add(renderHtml()); + app.fallback(renderHtml()); // You can pass a custom StringRenderer if you need more control over the output. - app.before.add(renderHtml(renderer: new StringRenderer(html5: false))); + app.fallback(renderHtml(renderer: new StringRenderer(html5: false))); app.get('/greet/:name', (RequestContext req) { return html(lang: 'en', c: [ @@ -76,7 +76,7 @@ client doesn't accept `*/*` or `text/html`. ```dart configureServer(Angel app) async { // Wire it up! - app.before.add(renderHtml(enforceAcceptHeader: true)); + app.fallback(renderHtml(enforceAcceptHeader: true)); // ... } diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..c230cee7 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 00000000..d1570ec3 --- /dev/null +++ b/example/main.dart @@ -0,0 +1,46 @@ +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; +import 'package:angel_html/angel_html.dart'; +import 'package:html_builder/elements.dart'; +import 'package:logging/logging.dart'; + +main() async { + var app = Angel(), http = AngelHttp(app); + app.logger = Logger('angel_html') + ..onRecord.listen((rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(rec.stackTrace); + }); + + app.fallback(renderHtml()); + + app.get('/html', (req, res) { + return html(c: [ + head(c: [ + title(c: [text('ok')]) + ]) + ]); + }); + + app.get( + '/strict', + chain([ + renderHtml( + enforceAcceptHeader: true, + renderer: StringRenderer( + doctype: null, + pretty: false, + ), + ), + (req, res) { + return div(c: [text('strict')]); + }, + ]), + ); + + app.fallback((req, res) => throw AngelHttpException.notFound()); + + await http.startServer('127.0.0.1', 3000); + print('Listening at ${http.uri}'); +} diff --git a/lib/angel_html.dart b/lib/angel_html.dart index 99563629..81b7c853 100644 --- a/lib/angel_html.dart +++ b/lib/angel_html.dart @@ -7,8 +7,7 @@ import 'package:html_builder/html_builder.dart'; /// You can provide a custom [renderer]. The default renders minified HTML5 pages. /// /// Set [enforceAcceptHeader] to `true` to throw a `406 Not Acceptable` if the client doesn't accept HTML responses. -RequestMiddleware renderHtml( - {StringRenderer renderer, bool enforceAcceptHeader}) { +RequestHandler renderHtml({StringRenderer renderer, bool enforceAcceptHeader}) { renderer ??= new StringRenderer(pretty: false, html5: true); return (RequestContext req, ResponseContext res) { @@ -21,11 +20,11 @@ RequestMiddleware renderHtml( if (enforceAcceptHeader == true && !req.accepts('text/html')) throw new AngelHttpException.notAcceptable(); - var content = renderer.render(data); + var content = renderer.render(data as Node); res ..headers['content-type'] = 'text/html' - ..write(content) - ..end(); + ..write(content); + res.close(); return ''; } }; diff --git a/pubspec.yaml b/pubspec.yaml index 7cfb322a..4851fd05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,14 +1,16 @@ name: angel_html -version: 1.0.0 -description: Easily render html_builder AST's as responses in Angel. +version: 2.0.0 +description: Support for rendering html_builder AST's as responses in Angel. author: Tobe O homepage: https://github.com/angel-dart/html_builder environment: - sdk: ">=1.19.0" + sdk: ">=2.0.0-dev <3.0.0" dependencies: - angel_framework: ^1.0.0 + angel_framework: ^2.0.0-alpha html_builder: ^1.0.0 dev_dependencies: - angel_test: ^1.0.0 + angel_test: ^2.0.0-alpha html: ^0.13.2 - test: ^0.12.0 \ No newline at end of file + logging: ^0.11.0 + test: ^1.0.0 + pedantic: ^1.0.0 \ No newline at end of file diff --git a/test/all_test.dart b/test/all_test.dart index 92795a83..e6777418 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -12,9 +12,9 @@ main() { setUp(() async { app = new Angel(); - app.before.add(renderHtml()); + app.fallback(renderHtml()); - app.get('/html', () { + app.get('/html', (req, res) { return html(c: [ head(c: [ title(c: [text('ok')]) @@ -22,14 +22,21 @@ main() { ]); }); - app - .chain(renderHtml( - enforceAcceptHeader: true, - renderer: new StringRenderer(doctype: null, pretty: false))) - .get('/strict', () { - return div(c: [text('strict')]); - }); - + app.get( + '/strict', + chain([ + renderHtml( + enforceAcceptHeader: true, + renderer: new StringRenderer( + doctype: null, + pretty: false, + ), + ), + (req, res) { + return div(c: [text('strict')]); + }, + ]), + ); client = await connectTo(app); }); @@ -41,8 +48,10 @@ main() { expect( response, - allOf(hasContentType('text/html'), - hasBody('ok'))); + allOf( + hasContentType('text/html'), + hasBody( + 'ok'))); }); group('enforce accept header', () { @@ -62,8 +71,7 @@ main() { test('throws if incorrect or no accept', () async { var response = await client.get('/strict'); print('Response: ${response.body}'); - expect(response, - isAngelHttpException(statusCode: 406, message: '406 Not Acceptable')); + expect(response, hasStatus(406)); response = await client .get('/strict', headers: {'accept': 'application/json,text/xml'});