diff --git a/packages/mustache/.gitignore b/packages/mustache/.gitignore new file mode 100644 index 00000000..670f6708 --- /dev/null +++ b/packages/mustache/.gitignore @@ -0,0 +1,30 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock + +.idea +.dart_tool \ No newline at end of file diff --git a/packages/mustache/.travis.yml b/packages/mustache/.travis.yml new file mode 100644 index 00000000..3939d628 --- /dev/null +++ b/packages/mustache/.travis.yml @@ -0,0 +1 @@ +language: dart diff --git a/packages/mustache/CHANGELOG.md b/packages/mustache/CHANGELOG.md new file mode 100644 index 00000000..cde505dc --- /dev/null +++ b/packages/mustache/CHANGELOG.md @@ -0,0 +1,2 @@ +# 2.0.0 +* Angel 2 and Dart 2 support. \ No newline at end of file diff --git a/packages/mustache/LICENSE b/packages/mustache/LICENSE new file mode 100644 index 00000000..eb4ce33e --- /dev/null +++ b/packages/mustache/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 angel-dart + +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/packages/mustache/README.md b/packages/mustache/README.md new file mode 100644 index 00000000..8cb38563 --- /dev/null +++ b/packages/mustache/README.md @@ -0,0 +1,35 @@ +# mustache +[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) +[![version](https://img.shields.io/pub/v/angel_mustache.svg)](https://pub.dartlang.org/packages/angel_mustache) +[![build status](https://travis-ci.org/angel-dart/mustache.svg?branch=master)](https://travis-ci.org/angel-dart/mustache) + +Mustache (Handlebars) view generator for the [Angel](https://github.com/angel-dart/angel) +web server framework. + +Thanks so much @c4wrd for his help with bringing this project to life! + +# Installation +In `pubspec.yaml`: + +```yaml +dependencies: + angel_mustache: ^2.0.0 +``` + +# Usage +```dart +const FileSystem fs = const LocalFileSystem(); + +configureServer(Angel app) async { + // Run the plug-in + await app.configure(mustache(fs.directory('views'))); + + // Render `hello.mustache` + await res.render('hello', {'name': 'world'}); +} +``` + +# Options +- **partialsPath**: A path within the viewsDirectory to search for partials in. + Default is `./partials`. +- **fileExtension**: The file extension to search for. Default is `.mustache`. diff --git a/packages/mustache/analysis_options.yaml b/packages/mustache/analysis_options.yaml new file mode 100644 index 00000000..518eb901 --- /dev/null +++ b/packages/mustache/analysis_options.yaml @@ -0,0 +1,2 @@ +analyzer: + strong-mode: true \ No newline at end of file diff --git a/packages/mustache/example/main.dart b/packages/mustache/example/main.dart new file mode 100644 index 00000000..30467e60 --- /dev/null +++ b/packages/mustache/example/main.dart @@ -0,0 +1,16 @@ +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mustache/angel_mustache.dart'; +import 'package:file/file.dart'; +import 'package:file/local.dart'; + +const FileSystem fs = const LocalFileSystem(); + +configureServer(Angel app) async { + // Run the plug-in + await app.configure(mustache(fs.directory('views'))); + + // Render `hello.mustache` + app.get('/', (req, res) async { + await res.render('hello', {'name': 'world'}); + }); +} diff --git a/packages/mustache/lib/angel_mustache.dart b/packages/mustache/lib/angel_mustache.dart new file mode 100644 index 00000000..ce758fa2 --- /dev/null +++ b/packages/mustache/lib/angel_mustache.dart @@ -0,0 +1,32 @@ +library angel_mustache; + +import 'package:angel_framework/angel_framework.dart'; +import 'package:file/file.dart'; +import 'package:mustache4dart/mustache4dart.dart' show render; +import 'package:path/path.dart' as p; +import 'src/cache.dart'; +import 'src/mustache_context.dart'; + +mustache(Directory viewsDirectory, + {String fileExtension: '.mustache', String partialsPath: './partials'}) { + Directory partialsDirectory = viewsDirectory.fileSystem + .directory(p.join(p.fromUri(viewsDirectory.uri), partialsPath)); + + MustacheContext context = + new MustacheContext(viewsDirectory, partialsDirectory, fileExtension); + + MustacheViewCache cache = new MustacheViewCache(context); + + return (Angel app) async { + app.viewGenerator = (String name, [Map data]) async { + var partialsProvider; + partialsProvider = (String name) { + String template = cache.getPartialSync(name, app); + return render(template, data ?? {}, partial: partialsProvider); + }; + + String viewTemplate = await cache.getView(name, app); + return await render(viewTemplate, data ?? {}, partial: partialsProvider); + }; + }; +} diff --git a/packages/mustache/lib/src/cache.dart b/packages/mustache/lib/src/cache.dart new file mode 100644 index 00000000..6c8f92bd --- /dev/null +++ b/packages/mustache/lib/src/cache.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:collection'; +import 'package:file/file.dart'; + +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mustache/src/mustache_context.dart'; + +class MustacheViewCache { + /** + * The context for which views and partials are + * served from. + */ + MustacheContext context; + + HashMap viewCache = new HashMap(); + HashMap partialCache = new HashMap(); + + MustacheViewCache([this.context]); + + Future getView(String viewName, Angel app) async { + if (app.isProduction) { + if (viewCache.containsKey(viewName)) { + return viewCache[viewName]; + } + } + + File viewFile = context.resolveView(viewName); + + if (viewFile.existsSync()) { + String viewTemplate = await viewFile.readAsString(); + if (app.isProduction) { + this.viewCache[viewName] = viewTemplate; + } + return viewTemplate; + } else + throw new FileSystemException( + 'View "$viewName" was not found.', viewFile.path); + } + + String getPartialSync(String partialName, Angel app) { + if (app.isProduction) { + if (partialCache.containsKey(partialName)) { + return partialCache[partialName]; + } + } + + File partialFile = context.resolvePartial(partialName); + + if (partialFile.existsSync()) { + String partialTemplate = partialFile.readAsStringSync(); + if (app.isProduction) { + this.partialCache[partialName] = partialTemplate; + } + return partialTemplate; + } else + throw new FileSystemException( + 'View "$partialName" was not found.', partialFile.path); + } +} diff --git a/packages/mustache/lib/src/mustache_context.dart b/packages/mustache/lib/src/mustache_context.dart new file mode 100644 index 00000000..9461f82e --- /dev/null +++ b/packages/mustache/lib/src/mustache_context.dart @@ -0,0 +1,20 @@ +import 'package:file/file.dart'; +import 'package:path/path.dart' as path; + +class MustacheContext { + Directory viewDirectory; + + Directory partialDirectory; + + String extension; + + MustacheContext([this.viewDirectory, this.partialDirectory, this.extension]); + + File resolveView(String viewName) { + return viewDirectory.childFile('${viewName}${extension}'); + } + + File resolvePartial(String partialName) { + return partialDirectory.childFile('${partialName}${extension}'); + } +} diff --git a/packages/mustache/mustache.iml b/packages/mustache/mustache.iml new file mode 100644 index 00000000..5a5ced28 --- /dev/null +++ b/packages/mustache/mustache.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/mustache/pubspec.yaml b/packages/mustache/pubspec.yaml new file mode 100644 index 00000000..430cb43e --- /dev/null +++ b/packages/mustache/pubspec.yaml @@ -0,0 +1,15 @@ +name: angel_mustache +description: Mustache view generator for Angel. +author: thosakwe +homepage: https://github.com/angel-dart/angel_mustache +version: 2.0.0 +environment: + sdk: ">=2.0.0-dev <3.0.0" +dependencies: + angel_framework: ^2.0.0-alpha + file: ^5.0.0 + mustache4dart: ^3.0.0-dev + path: ^1.0.0 +dev_dependencies: + http: + test: diff --git a/packages/mustache/test/all_test.dart b/packages/mustache/test/all_test.dart new file mode 100644 index 00000000..cc5c859f --- /dev/null +++ b/packages/mustache/test/all_test.dart @@ -0,0 +1,30 @@ +import 'dart:async'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mustache/angel_mustache.dart'; +import 'package:file/local.dart'; +import 'package:test/test.dart'; + +main() async { + Angel angel = new Angel(); + await angel.configure(mustache(const LocalFileSystem().directory('./test'))); + + test('can render templates', () async { + var hello = await angel.viewGenerator('hello', {'name': 'world'}); + var bar = await angel.viewGenerator('foo/bar', {'framework': 'angel'}); + + expect(hello, equals("Hello, world!")); + expect(bar, equals("angel_framework")); + }); + + test('throws if view is not found', () { + expect(new Future(() async { + var fails = await angel.viewGenerator('fail', {'this_should': 'fail'}); + print(fails); + }), throws); + }); + + test("partials", () async { + var withPartial = await angel.viewGenerator('with-partial'); + expect(withPartial, equals("Hello, world!")); + }); +} diff --git a/packages/mustache/test/foo/bar.mustache b/packages/mustache/test/foo/bar.mustache new file mode 100644 index 00000000..d71ee58f --- /dev/null +++ b/packages/mustache/test/foo/bar.mustache @@ -0,0 +1 @@ +{{framework}}_framework \ No newline at end of file diff --git a/packages/mustache/test/hello.mustache b/packages/mustache/test/hello.mustache new file mode 100644 index 00000000..e7773335 --- /dev/null +++ b/packages/mustache/test/hello.mustache @@ -0,0 +1 @@ +Hello, {{name}}! \ No newline at end of file diff --git a/packages/mustache/test/partials/name.mustache b/packages/mustache/test/partials/name.mustache new file mode 100644 index 00000000..04fea064 --- /dev/null +++ b/packages/mustache/test/partials/name.mustache @@ -0,0 +1 @@ +world \ No newline at end of file diff --git a/packages/mustache/test/with-partial.mustache b/packages/mustache/test/with-partial.mustache new file mode 100644 index 00000000..74c0e738 --- /dev/null +++ b/packages/mustache/test/with-partial.mustache @@ -0,0 +1 @@ +Hello, {{> name}}! \ No newline at end of file