Packaging
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..987f53d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+out
+packages
+/pubspec.lock
diff --git a/check.sh b/check.sh
new file mode 100755
index 0000000..28d4e40
--- /dev/null
+++ b/check.sh
@@ -0,0 +1,2 @@
+dart_analyzer --type-checks-for-inferred-types lib/mustache.dart
+
diff --git a/lib/mustache.dart b/lib/mustache.dart
new file mode 100644
index 0000000..ef33df0
--- /dev/null
+++ b/lib/mustache.dart
@@ -0,0 +1,11 @@
+library mustache;
+
+part 'template.dart';
+part 'scanner.dart';
+
+render(String source, values) => new Template(source).render(values);
+
+abstract class Template {
+ factory Template(String source) => new _Template(source);
+ String render(values);
+}
diff --git a/scanner.dart b/lib/scanner.dart
similarity index 75%
rename from scanner.dart
rename to lib/scanner.dart
index cc34e58..4c1cee2 100644
--- a/scanner.dart
+++ b/lib/scanner.dart
@@ -1,19 +1,14 @@
-library mustache.scanner;
+part of mustache;
-List<Token> scan(String source) => new _Scanner(source).scan();
+List<_Token> _scan(String source) => new _Scanner(source).scan();
-abstract class Token {
- int get type;
- String get value;
-}
-
-const int TEXT = 1;
-const int VARIABLE = 2;
-const int PARTIAL = 3;
-const int OPEN_SECTION = 4;
-const int OPEN_INV_SECTION = 5;
-const int CLOSE_SECTION = 6;
-const int COMMENT = 7;
+const int _TEXT = 1;
+const int _VARIABLE = 2;
+const int _PARTIAL = 3;
+const int _OPEN_SECTION = 4;
+const int _OPEN_INV_SECTION = 5;
+const int _CLOSE_SECTION = 6;
+const int _COMMENT = 7;
tokenTypeString(int type) => ['?', 'Text', 'Var', 'Par', 'Open', 'OpenInv', 'Close', 'Comment'][type];
@@ -32,7 +27,7 @@
const int _OPEN_MUSTACHE = 123;
const int _CLOSE_MUSTACHE = 125;
-class _Token implements Token {
+class _Token {
_Token(this.type, this.value);
_Token.fromChar(this.type, int charCode)
: value = new String.fromCharCode(charCode);
@@ -45,12 +40,12 @@
_Scanner(String source) : _r = new _CharReader(source);
_CharReader _r;
- List<Token> _tokens = new List<Token>();
+ List<_Token> _tokens = new List<_Token>();
int _read() => _r.read();
int _peek() => _r.peek();
- _add(Token t) => _tokens.add(t);
+ _add(_Token t) => _tokens.add(t);
_expect(int c) {
if (c != _read())
@@ -60,7 +55,7 @@
String _readString() => _r.readWhile(
(c) => c != _OPEN_MUSTACHE && c != _CLOSE_MUSTACHE && c != _EOF);
- List<Token> scan() {
+ List<_Token> scan() {
while(true) {
switch(_peek()) {
case _EOF:
@@ -83,10 +78,10 @@
return;
case _CLOSE_MUSTACHE:
_read();
- _add(new _Token.fromChar(TEXT, _CLOSE_MUSTACHE));
+ _add(new _Token.fromChar(_TEXT, _CLOSE_MUSTACHE));
break;
default:
- _add(new _Token(TEXT, _readString()));
+ _add(new _Token(_TEXT, _readString()));
}
}
}
@@ -96,7 +91,7 @@
_read();
if (_peek() != _OPEN_MUSTACHE) {
- _add(new _Token.fromChar(TEXT, _OPEN_MUSTACHE));
+ _add(new _Token.fromChar(_TEXT, _OPEN_MUSTACHE));
return;
}
@@ -108,49 +103,49 @@
// Escaped text {{{ ... }}}
case _OPEN_MUSTACHE:
_read();
- _add(new _Token(TEXT, _readString()));
+ _add(new _Token(_TEXT, _readString()));
_expect(_CLOSE_MUSTACHE);
break;
// Escaped text {{& ... }}
case _AMP:
_read();
- _add(new _Token(TEXT, _readString()));
+ _add(new _Token(_TEXT, _readString()));
break;
// Comment {{! ... }}
case _EXCLAIM:
_read();
- _add(new _Token(COMMENT, _readString()));
+ _add(new _Token(_COMMENT, _readString()));
break;
// Partial {{> ... }}
case _GT:
_read();
- _add(new _Token(PARTIAL, _readString()));
+ _add(new _Token(_PARTIAL, _readString()));
break;
// Open section {{# ... }}
case _HASH:
_read();
- _add(new _Token(OPEN_SECTION, _readString()));
+ _add(new _Token(_OPEN_SECTION, _readString()));
break;
// Open inverted section {{^ ... }}
case _CARET:
_read();
- _add(new _Token(OPEN_INV_SECTION, _readString()));
+ _add(new _Token(_OPEN_INV_SECTION, _readString()));
break;
// Close section {{/ ... }}
case _FORWARD_SLASH:
_read();
- _add(new _Token(CLOSE_SECTION, _readString()));
+ _add(new _Token(_CLOSE_SECTION, _readString()));
break;
// Variable {{ ... }}
default:
- _add(new _Token(VARIABLE, _readString()));
+ _add(new _Token(_VARIABLE, _readString()));
}
_expect(_CLOSE_MUSTACHE);
@@ -158,8 +153,6 @@
}
}
-
-//FIXME return _EOF
class _CharReader {
_CharReader(String source)
: _source = source,
diff --git a/lib/template.dart b/lib/template.dart
new file mode 100644
index 0000000..fe0d7b7
--- /dev/null
+++ b/lib/template.dart
@@ -0,0 +1,129 @@
+part of mustache;
+
+// http://mustache.github.com/mustache.5.html
+
+class Node {
+ Node(this.type, this.value);
+ final int type;
+ final String value;
+ final List<Node> children = new List<Node>();
+ String toString() => 'Node: ${tokenTypeString(type)}';
+}
+
+Node _parse(List<_Token> tokens) {
+ var stack = new List<Node>()..add(new Node(_OPEN_SECTION, 'root'));
+ for (var t in tokens) {
+ if (t.type == _TEXT || t.type == _VARIABLE) {
+ stack.last.children.add(new Node(t.type, t.value));
+ } else if (t.type == _OPEN_SECTION || t.type == _OPEN_INV_SECTION) {
+ var child = new Node(t.type, t.value);
+ stack.last.children.add(child);
+ stack.add(child);
+ } else if (t.type == _CLOSE_SECTION) {
+ assert(stack.last.value == t.value); //FIXME throw an exception if these don't match.
+ stack.removeLast();
+ } else {
+ throw new UnimplementedError();
+ }
+ }
+
+ return stack.last;
+}
+
+class _Template {
+ _Template(String source)
+ : _root = _parse(_scan(source));
+
+ final Node _root;
+ final ctl = new List(); //TODO StreamController();
+ final stack = new List();
+
+ render(values) {
+ ctl.clear();
+ stack.clear();
+ stack.add(values);
+ _root.children.forEach(_renderNode);
+ return ctl;
+ }
+
+ _renderNode(node) {
+ switch (node.type) {
+ case _TEXT:
+ _renderText(node);
+ break;
+ case _VARIABLE:
+ _renderVariable(node);
+ break;
+ case _OPEN_SECTION:
+ _renderSection(node);
+ break;
+ case _OPEN_INV_SECTION:
+ _renderInvSection(node);
+ break;
+ default:
+ throw new UnimplementedError();
+ }
+ }
+
+ _renderText(node) {
+ ctl.add(node.value);
+ }
+
+ _renderVariable(node) {
+ final value = stack.last[node.value]; //TODO optional warning if variable is null or missing.
+ final s = _htmlEscape(value.toString());
+ ctl.add(s);
+ }
+
+ _renderSectionWithValue(node, value) {
+ stack.add(value);
+ node.children.forEach(_renderNode);
+ stack.removeLast();
+ }
+
+ _renderSection(node) {
+ final value = stack.last[node.value];
+ if (value is List) {
+ value.forEach((v) => _renderSectionWithValue(node, v));
+ } else if (value is Map) {
+ _renderSectionWithValue(node, value);
+ } else if (value == true) {
+ _renderSectionWithValue(node, {});
+ } else {
+ print('boom!'); //FIXME
+ }
+ }
+
+ _renderInvSection(node) {
+ final val = stack.last[node.value];
+ if ((val is List && val.isEmpty)
+ || val == null
+ || val == false) {
+ _renderSectionWithValue(node, {});
+ }
+ }
+
+ /*
+ escape
+
+ & --> &
+ < --> <
+ > --> >
+ " --> "
+ ' --> ' ' not recommended because its not in the HTML spec (See: section 24.4.1) ' is in the XML and XHTML specs.
+ / --> /
+ */
+ //TODO
+ String _htmlEscape(String s) {
+ return s;
+ }
+}
+
+_visit(Node root, visitor(Node n)) {
+ var stack = new List<Node>()..add(root);
+ while (!stack.isEmpty) {
+ var node = stack.removeLast();
+ stack.addAll(node.children);
+ visitor(node);
+ }
+}
diff --git a/mustache.dart b/mustache.dart
deleted file mode 100644
index fce8a7c..0000000
--- a/mustache.dart
+++ /dev/null
@@ -1,188 +0,0 @@
-library mustache;
-
-import 'scanner.dart';
-
-var scannerTests = [
-'_{{variable}}_',
-'_{{variable}}',
-'{{variable}}_',
-'{{variable}}',
-' { ',
-' } ',
-' {} ',
-' }{} ',
-'{{{escaped text}}}',
-'{{&escaped text}}',
-'{{!comment}}',
-'{{#section}}oi{{/section}}',
-'{{^section}}oi{{/section}}',
-'{{>partial}}'
-];
-
-main2() {
- for (var src in scannerTests) {
- print('${_pad(src, 40)}${scan(src)}');
- }
-}
-
-main() {
- var source = '{{#section}}_{{var}}_{{/section}}';
- var tokens = scan(source);
- var node = _parse(tokens);
- //var output = render(node, {"section": {"var": "bob"}});
-
- var t = new Template(node);
- var output = t.render({"section": {"var": "bob"}});
-
- //_visit(node, (n) => print('visit: $n'));
- print(source);
- print(tokens);
- print(node);
- print(output);
-}
-
-_pad(String s, int len) {
- for (int i = s.length; i < len; i++)
- s = s + ' ';
- return s;
-}
-
-// http://mustache.github.com/mustache.5.html
-
-class Node {
- Node(this.type, this.value);
- final int type;
- final String value;
- final List<Node> children = new List<Node>();
-
- //FIXME
- toString() => stringify(0);
-
- stringify(int indent) {
- var pad = '';
- for (int i = 0; i < indent; i++)
- pad = '$pad-';
- var s = '$pad${tokenTypeString(type)} $value\n';
- ++indent;
- for (var c in children) {
- s += c.stringify(indent);
- }
- return s;
- }
-}
-
-Node _parse(List<Token> tokens) {
- var stack = new List<Node>()..add(new Node(OPEN_SECTION, 'root'));
- for (var t in tokens) {
- if (t.type == TEXT || t.type == VARIABLE) {
- stack.last.children.add(new Node(t.type, t.value));
- } else if (t.type == OPEN_SECTION || t.type == OPEN_INV_SECTION) {
- var child = new Node(t.type, t.value);
- stack.last.children.add(child);
- stack.add(child);
- } else if (t.type == CLOSE_SECTION) {
- assert(stack.last.value == t.value); //FIXME throw an exception if these don't match.
- stack.removeLast();
- } else {
- throw new UnimplementedError();
- }
- }
-
- return stack.last;
-}
-
-class Template {
- Template(this._root);
- final Node _root;
- final ctl = new List(); //TODO use streams.
- final stack = new List();
-
- render(values) {
- ctl.clear();
- stack.clear();
- stack.add(values);
- _root.children.forEach(_renderNode);
- return ctl;
- }
-
- _renderNode(node) {
- switch (node.type) {
- case TEXT:
- _renderText(node);
- break;
- case VARIABLE:
- _renderVariable(node);
- break;
- case OPEN_SECTION:
- _renderSection(node);
- break;
- case OPEN_INV_SECTION:
- _renderInvSection(node);
- break;
- default:
- throw new UnimplementedError();
- }
- }
-
- _renderText(node) {
- ctl.add(node.value);
- }
-
- _renderVariable(node) {
- final value = stack.last[node.value]; //TODO optional warning if variable is null or missing.
- final s = _htmlEscape(value.toString());
- ctl.add(s);
- }
-
- _renderSectionWithValue(node, value) {
- stack.add(value);
- node.children.forEach(_renderNode);
- stack.removeLast();
- }
-
- _renderSection(node) {
- final value = stack.last[node.value];
- if (value is List) {
- value.forEach((v) => _renderSectionWithValue(node, v));
- } else if (value is Map) {
- _renderSectionWithValue(node, value);
- } else if (value == true) {
- _renderSectionWithValue(node, {});
- } else {
- print('boom!'); //FIXME
- }
- }
-
- _renderInvSection(node) {
- final val = stack.last[node.value];
- if ((val is List && val.isEmpty)
- || val == null
- || val == false) {
- _renderSectionWithValue(node, {});
- }
- }
-
- /*
- escape
-
- & --> &
- < --> <
- > --> >
- " --> "
- ' --> ' ' not recommended because its not in the HTML spec (See: section 24.4.1) ' is in the XML and XHTML specs.
- / --> /
- */
- //TODO
- String _htmlEscape(String s) {
- return s;
- }
-}
-
-_visit(Node root, visitor(Node n)) {
- var stack = new List<Node>()..add(root);
- while (!stack.isEmpty) {
- var node = stack.removeLast();
- stack.addAll(node.children);
- visitor(node);
- }
-}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..eef5e32
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,7 @@
+name: mustache
+version: 0.1.0
+author: Greg Lowe <greg@vis.net.nz>
+description: Mustache template library
+homepage: https://github.com/xxgreg/mustache
+dev_dependencies:
+ unittest: any
\ No newline at end of file
diff --git a/test/mustache_test.dart b/test/mustache_test.dart
new file mode 100644
index 0000000..4493e57
--- /dev/null
+++ b/test/mustache_test.dart
@@ -0,0 +1,54 @@
+library mustache_test;
+
+import 'package:mustache/mustache.dart';
+
+var scannerTests = [
+'_{{variable}}_',
+'_{{variable}}',
+'{{variable}}_',
+'{{variable}}',
+' { ',
+' } ',
+' {} ',
+' }{} ',
+'{{{escaped text}}}',
+'{{&escaped text}}',
+'{{!comment}}',
+'{{#section}}oi{{/section}}',
+'{{^section}}oi{{/section}}',
+'{{>partial}}'
+];
+
+main() {
+ tokens();
+
+ var source = '{{#section}}_{{var}}_{{/section}}';
+ var t = new Template(source);
+ var output = t.render({"section": {"var": "bob"}});
+ print(source);
+ print(output);
+}
+
+tokens() {
+ for (var src in scannerTests) {
+ print('${_pad(src, 40)}${_scan(src)}');
+ }
+}
+
+_pad(String s, int len) {
+ for (int i = s.length; i < len; i++)
+ s = s + ' ';
+ return s;
+}
+
+ _stringify(Node n, int indent) {
+ var pad = '';
+ for (int i = 0; i < indent; i++)
+ pad = '$pad-';
+ var s = '$pad${tokenTypeString(type)} $value\n';
+ ++indent;
+ for (var c in n.children) {
+ s += _stringify(c, indent);
+ }
+ return s;
+ }