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

+

+	& --> &amp;

+	< --> &lt;

+	> --> &gt;

+	" --> &quot;

+	' --> &#x27;     &apos; not recommended because its not in the HTML spec (See: section 24.4.1) &apos; is in the XML and XHTML specs.

+	/ --> &#x2F; 

+	*/

+	//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

-

-	& --> &amp;

-	< --> &lt;

-	> --> &gt;

-	" --> &quot;

-	' --> &#x27;     &apos; not recommended because its not in the HTML spec (See: section 24.4.1) &apos; is in the XML and XHTML specs.

-	/ --> &#x2F; 

-	*/

-	//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;
+	}