[Pkg-javascript-commits] [node-snapdragon] 01/10: New upstream version 0.11.0

Praveen Arimbrathodiyil praveen at moszumanska.debian.org
Fri Jan 12 11:26:32 UTC 2018


This is an automated email from the git hooks/post-receive script.

praveen pushed a commit to branch master
in repository node-snapdragon.

commit 82ab04e250eee706ebcf1bd04a8be86244716169
Author: Pirate Praveen <praveen at debian.org>
Date:   Thu Jan 11 22:28:32 2018 +0530

    New upstream version 0.11.0
---
 .gitignore                                         |   8 +-
 .travis.yml                                        |   7 +
 .verb.md                                           | 188 ++++---
 LICENSE                                            |   2 +-
 README.md                                          | 610 +++++++++++++++++----
 docs/compiling.md                                  |   9 +
 docs/core-concepts.md                              |  54 ++
 docs/crash-course.md                               |  92 ++++
 docs/getting-started.md                            |  28 +
 docs/options.md                                    |   3 +
 docs/overview.md                                   |  38 ++
 docs/parsing.md                                    |  86 +++
 docs/plugins.md                                    |  18 +
 example.js                                         |   9 -
 examples/dot.js                                    |  29 +
 examples/errors.js                                 |  29 +
 examples/extglob.js                                |  90 ---
 examples/guide-examples.js                         |   9 +
 examples/parser.js                                 |  43 ++
 examples/tiny-globs.js                             |  50 ++
 index.js                                           | 250 +++++----
 lib/compiler.js                                    | 237 +++++---
 lib/error.js                                       |  23 +
 lib/parser.js                                      | 550 ++++++++++---------
 lib/position.js                                    |   6 +-
 lib/source-maps.js                                 |  27 +-
 lib/utils.js                                       |  48 --
 package.json                                       |  57 +-
 support/src/content/compiling.md                   |   9 +
 support/src/content/core-concepts.md               |  56 ++
 support/src/content/crash-course.md                |  95 ++++
 support/src/content/getting-started.md             |  29 +
 .../content/guides/creating-your-first-parser.md   | 124 +++++
 support/src/content/options.md                     |   3 +
 support/src/content/overview.md                    |  40 ++
 support/src/content/parsing.md                     |  92 ++++
 support/src/content/plugins.md                     |  18 +
 test/compile.js                                    |  24 +-
 test/nodes.js                                      |  70 +++
 test/parse.js                                      |  46 +-
 test/parser.js                                     |   1 +
 test/snapdragon.capture.js                         |  24 +-
 test/{compile.js => snapdragon.compile.js}         |  46 +-
 test/snapdragon.regex.js                           |   2 +
 test/test.js                                       |  11 -
 test/utils.js                                      |  13 -
 verbfile.js                                        |  13 +
 47 files changed, 2430 insertions(+), 886 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7988154..0a16ee9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,14 +6,20 @@
 test/actual
 actual
 coverage
+.nyc*
 
 # npm
 node_modules
 npm-debug.log
 
+# yarn
+yarn.lock
+yarn-error.log
+
 # misc
 _gh_pages
-benchmark
+_draft
+_drafts
 bower_components
 vendor
 temp
diff --git a/.travis.yml b/.travis.yml
index b5dfcbb..58dcd48 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,13 @@
 sudo: false
+os:
+  - linux
+  - osx
 language: node_js
 node_js:
   - node
+  - '7'
   - '6'
   - '5'
+  - '4'
+  - '0.12'
+  - '0.10'
diff --git a/.verb.md b/.verb.md
index 70a2e39..c339836 100644
--- a/.verb.md
+++ b/.verb.md
@@ -6,74 +6,86 @@ Created by [jonschlinkert]({%= author.url %}) and [doowb](https://github.com/doo
 - All parsing and compiling is handled by simple, reusable middleware functions
 - Inspired by the parsers in [pug][] and [css][].
 
-## History
-
-### v0.5.0
-
-**Breaking changes**
-
-Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class. 
-
-- Renderer was renamed to `Compiler`
-- the `.render` method was renamed to `.compile`
-- Many other smaller changes. A more detailed overview will be provided in 0.6.0. If you don't have to time review code, I recommend you wait for the 0.6.0 release.
+## Quickstart example
 
-
-## Usage examples
+All of the examples in this document assume the following two lines of setup code exist first:
 
 ```js
 var Snapdragon = require('{%= name %}');
 var snapdragon = new Snapdragon();
 ```
 
-**Parse**
+**Parse a string**
 
 ```js
-var ast = snapdragon.parser('some string', options)
-  // parser middleware that can be called by other middleware
+var ast = snapdragon.parser
+  // parser handlers (essentially middleware)
+  // used for parsing substrings to create tokens
   .set('foo', function () {})
-  // parser middleware, runs immediately in the order defined
-  .use(bar())
-  .use(baz())
+  .set('bar', function () {})
+  .parse('some string', options);
 ```
 
-**Render**
+**Compile an AST returned from `.parse()`**
 
 ```js
-// pass the `ast` from the parse method
-var res = snapdragon.compiler(ast)
-  // compiler middleware, called when the name of the middleware
-  // matches the `node.type` (defined in a parser middleware)
+var result = snapdragon.compiler
+  // compiler handlers (essentially middleware), 
+  // called on a node when the `node.type` matches
+  // the name of the handler
+  .set('foo', function () {})
   .set('bar', function () {})
-  .set('baz', function () {})
-  .compile()
+  // pass the `ast` from the parse method
+  .compile(ast)
+
+// the compiled string
+console.log(result.output);
 ```
 
 See the [examples](./examples/).
 
-## Getting started
+## Parsing
+
+**Parser handlers**
 
-**Parsers**
+Parser handlers are middleware functions responsible for matching substrings to create tokens:
 
-Parsers are middleware functions used for parsing a string into an ast node.
+**Example handler**
 
 ```js
-var ast = snapdragon.parser(str, options)
-  .use(function() {
+var ast = snapdragon.parser
+  .set('dot', function() {
     var pos = this.position();
     var m = this.match(/^\./);
     if (!m) return;
     return pos({
-      // `type` specifies the compiler to use
+      // the "type" will be used by the compiler later on,
+      // we'll go over this in the compiler docs
       type: 'dot',
+      // "val" is the string captured by ".match",
+      // in this case that would be '.'
       val: m[0]
     });
   })
+  .parse('.'[, options])
+```
+
+_As a side node, it's not scrictly required to set the `type` on the token, since the parser will add it to the token if it's undefined, based on the name of the handler. But it's good practice since tokens aren't always returned._
+
+**Example token**
+
+And the resulting tokens look something like this:
+
+```js
+{ 
+  type: 'dot',
+  val: '.' 
+}
 ```
 
-**AST node**
+**Position**
 
-When the parser finds a match, `pos()` is called, pushing a token for that node onto the ast that looks something like:
+Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured:
 
 ```js
 { type: 'dot',
@@ -83,13 +95,30 @@ When the parser finds a match, `pos()` is called, pushing a token for that node
      end: { lineno: 1, column: 2 } }}
 ```
 
-**Renderers**
+**Life as an AST node**
+
+When the token is returned, the parser pushes it onto the `nodes` array of the "previous" node (since we're in a tree, the "previous" node might be literally the last node that was created, or it might be the "parent" node inside a nested context, like when parsing brackets or something with an open or close), at which point the token begins its life as an AST node.
+
+
+**Wrapping up**
+
+In the parser calls all handlers and cannot find a match for a substring, an error is thrown.
 
-Renderers are _named_ middleware functions that visit over an array of ast nodes to compile a string.
+Assuming the parser finished parsing the entire string, an AST is returned.
 
 
+## Compiling
+
+The compiler's job is to take the AST created by the [parser](#parsing) and convert it to a new string. It does this by iterating over each node on the AST and calling a function on the node based on its `type`.
+
+This function is called a "handler".
+
+**Compiler handlers**
+
+Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler.
+
 ```js
-var res = snapdragon.compiler(ast)
+var result = snapdragon.compiler
   .set('dot', function (node) {
     console.log(node.val)
     //=> '.'
@@ -97,72 +126,45 @@ var res = snapdragon.compiler(ast)
   })
 ```
 
+If `node.type` does not match a registered handler, an error is thrown.
+
+
 **Source maps**
 
-If you want source map support, make sure to emit the position as well.
+If you want source map support, make sure to emit the entire node as the second argument as well (this allows the compiler to get the `node.position`).
 
 ```js
-var res = snapdragon.compiler(ast)
+var res = snapdragon.compiler
   .set('dot', function (node) {
-    return this.emit(node.val, node.position);
+    return this.emit(node.val, node);
   })
 ```
 
-## Docs
-
-### Parser middleware
+## All together
 
-A parser middleware is a function that returns an abject called a `token`. This token is pushed onto the AST as a node.
-
-**Example token**
+This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
 
 ```js
-{ type: 'dot',
-  val: '.',
-  position:
-   { start: { lineno: 1, column: 1 },
-     end: { lineno: 1, column: 2 } }}
-```
-
-**Example parser middleware**
-
-Match a single `.` in a string:
-
-  1. Get the starting position by calling `this.position()`
-  1. pass a regex for matching a single dot to the `.match` method
-  1. if **no match** is found, return `undefined`
-  1. if a **match** is found, `pos()` is called, which returns a token with:
-      * `type`: the name of the [compiler] to use
-      * `val`: The actual value captured by the regex. In this case, a `.`. Note that you can capture and return whatever will be needed by the corresponding [compiler].
-      * The ending position: automatically calculated by adding the length of the first capture group to the starting position. 
-
-
-## Renderer middleware
-
-Renderers are run when the name of the compiler middleware matches the `type` defined on an ast `node` (which is defined in a parser).
-
-**Example**
-
-Exercise: Parse a dot, then compile it as an escaped dot.
+var Snapdragon = require('..');
+var snapdragon = new Snapdragon();
 
-```js
-var ast = snapdragon.parser('.')
-  .use(function () {
+var ast = snapdragon.parser
+  .set('dot', function () {
     var pos = this.position();
     var m = this.match(/^\./);
     if (!m) return;
     return pos({
-      // define the `type` of compiler to use
       type: 'dot',
       val: m[0]
     })
   })
+  .parse('.')
 
-var result = snapdragon.compiler(ast)
+var result = snapdragon.compiler
   .set('dot', function (node) {
     return this.emit('\\' + node.val);
   })
-  .compile()
+  .compile(ast)
 
 console.log(result.output);
 //=> '\.'
@@ -173,5 +175,31 @@ console.log(result.output);
 ### Parse
 {%= apidocs("lib/parser.js") %}
 
-### Render
+### Compile
 {%= apidocs("lib/compiler.js") %}
+
+
+## Snapdragon in the wild
+{%= verb.related.description %}
+{%= related(verb.related.implementations) %}
+
+## History
+
+### v0.9.0
+
+**Breaking changes!**
+
+In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release. 
+
+- `parser.capture` was externalized to [snapdragon-capture][]
+- `parser.capturePair` was externalized to [snapdragon-capture-set][]
+- Nodes are now an instance of [snapdragon-node][]
+
+### v0.5.0
+
+**Breaking changes!**
+
+Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class. 
+
+- Renderer was renamed to `Compiler`
+- the `.render` method was renamed to `.compile`
diff --git a/LICENSE b/LICENSE
index 1e49edf..83b56e7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015-2016, Jon Schlinkert.
+Copyright (c) 2015-2017, Jon Schlinkert
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 4006e10..da5529f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,23 @@
-# snapdragon [![NPM version](https://img.shields.io/npm/v/snapdragon.svg?style=flat)](https://www.npmjs.com/package/snapdragon) [![NPM downloads](https://img.shields.io/npm/dm/snapdragon.svg?style=flat)](https://npmjs.org/package/snapdragon) [![Build Status](https://img.shields.io/travis/jonschlinkert/snapdragon.svg?style=flat)](https://travis-ci.org/jonschlinkert/snapdragon)
-
-> Fast, pluggable and easy-to-use parser-renderer factory.
+# snapdragon [![NPM version](https://img.shields.io/npm/v/snapdragon.svg?style=flat)](https://www.npmjs.com/package/snapdragon) [![NPM monthly downloads](https://img.shields.io/npm/dm/snapdragon.svg?style=flat)](https://npmjs.org/package/snapdragon)  [![NPM total downloads](https://img.shields.io/npm/dt/snapdragon.svg?style=flat)](https://npmjs.org/package/snapdragon) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/snapdragon.svg?style=flat&label=Travis)](https://travi [...]
+
+> Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.
+
+<details>
+<summary><strong>Table of Contents</strong></summary>
+- [Install](#install)
+- [Quickstart example](#quickstart-example)
+- [Parsing](#parsing)
+- [Compiling](#compiling)
+- [All together](#all-together)
+- [API](#api)
+  * [Parse](#parse)
+  * [Compile](#compile)
+- [Snapdragon in the wild](#snapdragon-in-the-wild)
+- [History](#history)
+  * [v0.9.0](#v090)
+  * [v0.5.0](#v050)
+- [About](#about)
+</details>
 
 ## Install
 
@@ -16,75 +33,88 @@ Created by [jonschlinkert](https://github.com/jonschlinkert) and [doowb](https:/
 
 * Bootstrap your own parser, get sourcemap support for free
 * All parsing and compiling is handled by simple, reusable middleware functions
-* Inspired by the parsers in [pug](http://jade-lang.com) and [css](https://github.com/reworkcss/css).
-
-## History
-
-### v0.5.0
+* Inspired by the parsers in [pug](https://pugjs.org) and [css](https://github.com/reworkcss/css).
 
-**Breaking changes**
-
-Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class.
-
-* Renderer was renamed to `Compiler`
-* the `.render` method was renamed to `.compile`
-* Many other smaller changes. A more detailed overview will be provided in 0.6.0. If you don't have to time review code, I recommend you wait for the 0.6.0 release.
+## Quickstart example
 
-## Usage examples
+All of the examples in this document assume the following two lines of setup code exist first:
 
 ```js
 var Snapdragon = require('snapdragon');
 var snapdragon = new Snapdragon();
 ```
 
-**Parse**
+**Parse a string**
 
 ```js
-var ast = snapdragon.parser('some string', options)
-  // parser middleware that can be called by other middleware
+var ast = snapdragon.parser
+  // parser handlers (essentially middleware)
+  // used for parsing substrings to create tokens
   .set('foo', function () {})
-  // parser middleware, runs immediately in the order defined
-  .use(bar())
-  .use(baz())
+  .set('bar', function () {})
+  .parse('some string', options);
 ```
 
-**Render**
+**Compile an AST returned from `.parse()`**
 
 ```js
-// pass the `ast` from the parse method
-var res = snapdragon.compiler(ast)
-  // compiler middleware, called when the name of the middleware
-  // matches the `node.type` (defined in a parser middleware)
+var result = snapdragon.compiler
+  // compiler handlers (essentially middleware), 
+  // called on a node when the `node.type` matches
+  // the name of the handler
+  .set('foo', function () {})
   .set('bar', function () {})
-  .set('baz', function () {})
-  .compile()
+  // pass the `ast` from the parse method
+  .compile(ast)
+
+// the compiled string
+console.log(result.output);
 ```
 
 See the [examples](./examples/).
 
-## Getting started
+## Parsing
 
-**Parsers**
+**Parser handlers**
 
-Parsers are middleware functions used for parsing a string into an ast node.
+Parser handlers are middleware functions responsible for matching substrings to create tokens:
+
+**Example handler**
 
 ```js
-var ast = snapdragon.parser(str, options)
-  .use(function() {
+var ast = snapdragon.parser
+  .set('dot', function() {
     var pos = this.position();
     var m = this.match(/^\./);
     if (!m) return;
     return pos({
-      // `type` specifies the compiler to use
+      // the "type" will be used by the compiler later on,
+      // we'll go over this in the compiler docs
       type: 'dot',
+      // "val" is the string captured by ".match",
+      // in this case that would be '.'
       val: m[0]
     });
   })
+  .parse('.'[, options])
 ```
 
-**AST node**
+_As a side node, it's not scrictly required to set the `type` on the token, since the parser will add it to the token if it's undefined, based on the name of the handler. But it's good practice since tokens aren't always returned._
+
+**Example token**
 
-When the parser finds a match, `pos()` is called, pushing a token for that node onto the ast that looks something like:
+And the resulting tokens look something like this:
+
+```js
+{ 
+  type: 'dot',
+  val: '.' 
+}
+```
+
+**Position**
+
+Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured:
 
 ```js
 { type: 'dot',
@@ -94,12 +124,28 @@ When the parser finds a match, `pos()` is called, pushing a token for that node
      end: { lineno: 1, column: 2 } }}
 ```
 
-**Renderers**
+**Life as an AST node**
+
+When the token is returned, the parser pushes it onto the `nodes` array of the "previous" node (since we're in a tree, the "previous" node might be literally the last node that was created, or it might be the "parent" node inside a nested context, like when parsing brackets or something with an open or close), at which point the token begins its life as an AST node.
+
+**Wrapping up**
+
+In the parser calls all handlers and cannot find a match for a substring, an error is thrown.
+
+Assuming the parser finished parsing the entire string, an AST is returned.
+
+## Compiling
 
-Renderers are _named_ middleware functions that visit over an array of ast nodes to compile a string.
+The compiler's job is to take the AST created by the [parser](#parsing) and convert it to a new string. It does this by iterating over each node on the AST and calling a function on the node based on its `type`.
+
+This function is called a "handler".
+
+**Compiler handlers**
+
+Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler.
 
 ```js
-var res = snapdragon.compiler(ast)
+var result = snapdragon.compiler
   .set('dot', function (node) {
     console.log(node.val)
     //=> '.'
@@ -107,71 +153,44 @@ var res = snapdragon.compiler(ast)
   })
 ```
 
+If `node.type` does not match a registered handler, an error is thrown.
+
 **Source maps**
 
-If you want source map support, make sure to emit the position as well.
+If you want source map support, make sure to emit the entire node as the second argument as well (this allows the compiler to get the `node.position`).
 
 ```js
-var res = snapdragon.compiler(ast)
+var res = snapdragon.compiler
   .set('dot', function (node) {
-    return this.emit(node.val, node.position);
+    return this.emit(node.val, node);
   })
 ```
 
-## Docs
+## All together
 
-### Parser middleware
-
-A parser middleware is a function that returns an abject called a `token`. This token is pushed onto the AST as a node.
-
-**Example token**
+This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
 
 ```js
-{ type: 'dot',
-  val: '.',
-  position:
-   { start: { lineno: 1, column: 1 },
-     end: { lineno: 1, column: 2 } }}
-```
-
-**Example parser middleware**
-
-Match a single `.` in a string:
-
-1. Get the starting position by calling `this.position()`
-2. pass a regex for matching a single dot to the `.match` method
-3. if **no match** is found, return `undefined`
-4. if a **match** is found, `pos()` is called, which returns a token with:
-  - `type`: the name of the [compiler] to use
-  - `val`: The actual value captured by the regex. In this case, a `.`. Note that you can capture and return whatever will be needed by the corresponding [compiler].
-  - The ending position: automatically calculated by adding the length of the first capture group to the starting position.
-
-## Renderer middleware
-
-Renderers are run when the name of the compiler middleware matches the `type` defined on an ast `node` (which is defined in a parser).
-
-**Example**
-
-Exercise: Parse a dot, then compile it as an escaped dot.
+var Snapdragon = require('..');
+var snapdragon = new Snapdragon();
 
-```js
-var ast = snapdragon.parser('.')
-  .use(function () {
+var ast = snapdragon.parser
+  .set('dot', function () {
     var pos = this.position();
     var m = this.match(/^\./);
     if (!m) return;
     return pos({
-      // define the `type` of compiler to use
       type: 'dot',
       val: m[0]
     })
   })
+  .parse('.')
 
-var result = snapdragon.compiler(ast)
+var result = snapdragon.compiler
   .set('dot', function (node) {
     return this.emit('\\' + node.val);
   })
-  .compile()
+  .compile(ast)
 
 console.log(result.output);
 //=> '\.'
@@ -179,7 +198,7 @@ console.log(result.output);
 
 ## API
 
-### [Parser](lib/parser.js#L19)
+### [Parser](lib/parser.js#L27)
 
 Create a new `Parser` with the given `input` and `options`.
 
@@ -188,50 +207,170 @@ Create a new `Parser` with the given `input` and `options`.
 * `input` **{String}**
 * `options` **{Object}**
 
-### [.define](lib/parser.js#L103)
+**Example**
+
+```js
+var Snapdragon = require('snapdragon');
+var Parser = Snapdragon.Parser;
+var parser = new Parser();
+```
 
-Define a non-enumberable property on the `Parser` instance.
+### [.error](lib/parser.js#L94)
+
+Throw a formatted error message with details including the cursor position.
+
+**Params**
+
+* `msg` **{String}**: Message to use in the Error.
+* `node` **{Object}**
+* `returns` **{undefined}**
 
 **Example**
 
 ```js
-parser.define('foo', 'bar');
+parser.set('foo', function(node) {
+  if (node.val !== 'foo') {
+    throw this.error('expected node.val to be "foo"', node);
+  }
+});
 ```
 
+### [.define](lib/parser.js#L112)
+
+Define a non-enumberable property on the `Parser` instance. This is useful in plugins, for exposing methods inside handlers.
+
 **Params**
 
 * `key` **{String}**: propery name
 * `val` **{any}**: property value
 * `returns` **{Object}**: Returns the Parser instance for chaining.
 
-Set parser `name` with the given `fn`
+**Example**
+
+```js
+parser.define('foo', 'bar');
+```
+
+### [.node](lib/parser.js#L130)
+
+Create a new [Node](#node) with the given `val` and `type`.
 
 **Params**
 
-* `name` **{String}**
+* `val` **{Object}**
+* `type` **{String}**
+* `returns` **{Object}**: returns the [Node](#node) instance.
+
+**Example**
+
+```js
+parser.node('/', 'slash');
+```
+
+### [.position](lib/parser.js#L152)
+
+Mark position and patch `node.position`.
+
+* `returns` **{Function}**: Returns a function that takes a `node`
+
+**Example**
+
+```js
+parser.set('foo', function(node) {
+  var pos = this.position();
+  var match = this.match(/foo/);
+  if (match) {
+    // call `pos` with the node
+    return pos(this.node(match[0]));
+  }
+});
+```
+
+### [.set](lib/parser.js#L184)
+
+Add parser `type` with the given visitor `fn`.
+
+**Params**
+
+* `type` **{String}**
 * `fn` **{Function}**
 
-Get parser `name`
+**Example**
+
+```js
+ parser.set('all', function() {
+   var match = this.match(/^./);
+   if (match) {
+     return this.node(match[0]);
+   }
+ });
+```
+
+### [.get](lib/parser.js#L203)
+
+Get parser `type`.
 
 **Params**
 
-* `name` **{String}**
+* `type` **{String}**
+
+**Example**
+
+```js
+var fn = parser.get('slash');
+```
 
-Push a `token` onto the `type` stack.
+### [.push](lib/parser.js#L226)
+
+Push a node onto the stack for the given `type`.
 
 **Params**
 
 * `type` **{String}**
 * `returns` **{Object}** `token`
 
-Pop a token off of the `type` stack
+**Example**
+
+```js
+parser.set('all', function() {
+  var match = this.match(/^./);
+  if (match) {
+    var node = this.node(match[0]);
+    this.push(node);
+    return node;
+  }
+});
+```
+
+### [.pop](lib/parser.js#L256)
+
+Pop a token off of the stack of the given `type`.
 
 **Params**
 
 * `type` **{String}**
 * `returns` **{Object}**: Returns a token
 
-Return true if inside a `stack` node. Types are `braces`, `parens` or `brackets`.
+**Example**
+
+```js
+parser.set('close', function() {
+  var match = this.match(/^\}/);
+  if (match) {
+    var node = this.node({
+      type: 'close',
+      val: match[0]
+    });
+
+    this.pop(node.type);
+    return node;
+  }
+});
+```
+
+### [.isInside](lib/parser.js#L286)
+
+Return true if inside a "set" of the given `type`. Sets are created manually by adding a type to `parser.sets`. A node is "inside" a set when an `*.open` node for the given `type` was previously pushed onto the set. The type is removed from the set by popping it off when the `*.close` node for the given type is reached.
 
 **Params**
 
@@ -241,39 +380,290 @@ Return true if inside a `stack` node. Types are `braces`, `parens` or `brackets`
 **Example**
 
 ```js
-parser.isType(node, 'brace');
+parser.set('close', function() {
+  var pos = this.position();
+  var m = this.match(/^\}/);
+  if (!m) return;
+  if (!this.isInside('bracket')) {
+    throw new Error('missing opening bracket');
+  }
+});
 ```
 
+### [.isType](lib/parser.js#L303)
+
+Return true if `node` is the given `type`.
+
 **Params**
 
 * `node` **{Object}**
 * `type` **{String}**
 * `returns` **{Boolean}**
 
-### [.define](lib/compiler.js#L71)
+**Example**
+
+```js
+parser.isType(node, 'brace');
+```
+
+### [.prev](lib/parser.js#L319)
+
+Get the previous AST node from the `parser.stack` (when inside a nested context) or `parser.nodes`.
+
+* `returns` **{Object}**
+
+**Example**
+
+```js
+var prev = this.prev();
+```
+
+### [.prev](lib/parser.js#L373)
+
+Match `regex`, return captures, and update the cursor position by `match[0]` length.
+
+**Params**
+
+* `regex` **{RegExp}**
+* `returns` **{Object}**
 
-Define a non-enumberable property on the `Compiler` instance.
+**Example**
+
+```js
+// make sure to use the starting regex boundary: "^"
+var match = this.match(/^\./);
+```
+
+**Params**
+
+* `input` **{String}**
+* `returns` **{Object}**: Returns an AST with `ast.nodes`
 
 **Example**
 
 ```js
-compiler.define('foo', 'bar');
+var ast = parser.parse('foo/bar');
 ```
 
+### [Compiler](lib/compiler.js#L24)
+
+Create a new `Compiler` with the given `options`.
+
+**Params**
+
+* `options` **{Object}**
+* `state` **{Object}**: Optionally pass a "state" object to use inside visitor functions.
+
+**Example**
+
+```js
+var Snapdragon = require('snapdragon');
+var Compiler = Snapdragon.Compiler;
+var compiler = new Compiler();
+```
+
+### [.error](lib/compiler.js#L67)
+
+Throw a formatted error message with details including the cursor position.
+
+**Params**
+
+* `msg` **{String}**: Message to use in the Error.
+* `node` **{Object}**
+* `returns` **{undefined}**
+
+**Example**
+
+```js
+compiler.set('foo', function(node) {
+  if (node.val !== 'foo') {
+    throw this.error('expected node.val to be "foo"', node);
+  }
+});
+```
+
+### [.emit](lib/compiler.js#L86)
+
+Concat the given string to `compiler.output`.
+
+**Params**
+
+* `string` **{String}**
+* `node` **{Object}**: Optionally pass the node to use for position if source maps are enabled.
+* `returns` **{String}**: returns the string
+
+**Example**
+
+```js
+compiler.set('foo', function(node) {
+  this.emit(node.val, node);
+});
+```
+
+### [.noop](lib/compiler.js#L104)
+
+Emit an empty string to effectively "skip" the string for the given `node`, but still emit the position and node type.
+
+**Params**
+
+* **{Object}**: node
+
+**Example**
+
+```js
+// example: do nothing for beginning-of-string
+snapdragon.compiler.set('bos', compiler.noop);
+```
+
+### [.define](lib/compiler.js#L124)
+
+Define a non-enumberable property on the `Compiler` instance. This is useful in plugins, for exposing methods inside handlers.
+
 **Params**
 
 * `key` **{String}**: propery name
 * `val` **{any}**: property value
 * `returns` **{Object}**: Returns the Compiler instance for chaining.
 
-## About
+**Example**
 
-### Related projects
+```js
+compiler.define('customMethod', function() {
+  // do stuff
+});
+```
+
+### [.set](lib/compiler.js#L152)
+
+Add a compiler `fn` for the given `type`. Compilers are called when the `.compile` method encounters a node of the given type to generate the output string.
+
+**Params**
+
+* `type` **{String}**
+* `fn` **{Function}**
+
+**Example**
+
+```js
+compiler
+  .set('comma', function(node) {
+    this.emit(',');
+  })
+  .set('dot', function(node) {
+    this.emit('.');
+  })
+  .set('slash', function(node) {
+    this.emit('/');
+  });
+```
+
+### [.get](lib/compiler.js#L168)
+
+Get the compiler of the given `type`.
+
+**Params**
+
+* `type` **{String}**
+
+**Example**
+
+```js
+var fn = compiler.get('slash');
+```
+
+### [.visit](lib/compiler.js#L188)
+
+Visit `node` using the registered compiler function associated with the `node.type`.
+
+**Params**
 
-* [braces](https://www.npmjs.com/package/braces): Fastest brace expansion for node.js, with the most complete support for the Bash 4.3 braces… [more](https://github.com/jonschlinkert/braces) | [homepage](https://github.com/jonschlinkert/braces "Fastest brace expansion for node.js, with the most complete support for the Bash 4.3 braces specification.")
+* `node` **{Object}**
+* `returns` **{Object}**: returns the node
+
+**Example**
+
+```js
+compiler
+  .set('i', function(node) {
+    this.visit(node);
+  })
+```
+
+### [.mapVisit](lib/compiler.js#L226)
+
+Iterate over `node.nodes`, calling [visit](#visit) on each node.
+
+**Params**
+
+* `node` **{Object}**
+* `returns` **{Object}**: returns the node
+
+**Example**
+
+```js
+compiler
+  .set('i', function(node) {
+    utils.mapVisit(node);
+  })
+```
+
+### [.compile](lib/compiler.js#L250)
+
+Compile the given `AST` and return a string. Iterates over `ast.nodes` with [mapVisit](#mapVisit).
+
+**Params**
+
+* `ast` **{Object}**
+* `options` **{Object}**: Compiler options
+* `returns` **{Object}**: returns the node
+
+**Example**
+
+```js
+var ast = parser.parse('foo');
+var str = compiler.compile(ast);
+```
+
+## Snapdragon in the wild
+
+A few of the libraries that use snapdragon:
+
+* [braces](https://www.npmjs.com/package/braces): Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces… [more](https://github.com/jonschlinkert/braces) | [homepage](https://github.com/jonschlinkert/braces "Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces specification, without sacrificing speed.")
+* [breakdance](https://www.npmjs.com/package/breakdance): Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy… [more](http://breakdance.io) | [homepage](http://breakdance.io "Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy to use. It's time for your markup to get down.")
 * [expand-brackets](https://www.npmjs.com/package/expand-brackets): Expand POSIX bracket expressions (character classes) in glob patterns. | [homepage](https://github.com/jonschlinkert/expand-brackets "Expand POSIX bracket expressions (character classes) in glob patterns.")
-* [extglob](https://www.npmjs.com/package/extglob): Convert extended globs to regex-compatible strings. Add (almost) the expressive power of regular expressions to… [more](https://github.com/jonschlinkert/extglob) | [homepage](https://github.com/jonschlinkert/extglob "Convert extended globs to regex-compatible strings. Add (almost) the expressive power of regular expressions to glob patterns.")
+* [extglob](https://www.npmjs.com/package/extglob): Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob… [more](https://github.com/jonschlinkert/extglob) | [homepage](https://github.com/jonschlinkert/extglob "Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob patterns.")
 * [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
+* [nanomatch](https://www.npmjs.com/package/nanomatch): Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash… [more](https://github.com/jonschlinkert/nanomatch) | [homepage](https://github.com/jonschlinkert/nanomatch "Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash 4.3 wildcard support only (no support for exglobs, posix brackets or braces)")
+
+## History
+
+### v0.9.0
+
+**Breaking changes!**
+
+In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release.
+
+* `parser.capture` was externalized to [snapdragon-capture](https://github.com/jonschlinkert/snapdragon-capture)
+* `parser.capturePair` was externalized to [snapdragon-capture-set](https://github.com/jonschlinkert/snapdragon-capture-set)
+* Nodes are now an instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
+
+### v0.5.0
+
+**Breaking changes!**
+
+Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class.
+
+* Renderer was renamed to `Compiler`
+* the `.render` method was renamed to `.compile`
+
+## About
+
+### Related projects
+
+* [snapdragon-capture-set](https://www.npmjs.com/package/snapdragon-capture-set): Plugin that adds a `.captureSet()` method to snapdragon, for matching and capturing substrings that have… [more](https://github.com/jonschlinkert/snapdragon-capture-set) | [homepage](https://github.com/jonschlinkert/snapdragon-capture-set "Plugin that adds a `.captureSet()` method to snapdragon, for matching and capturing substrings that have an `open` and `close`, like braces, brackets, etc")
+* [snapdragon-capture](https://www.npmjs.com/package/snapdragon-capture): Snapdragon plugin that adds a capture method to the parser instance. | [homepage](https://github.com/jonschlinkert/snapdragon-capture "Snapdragon plugin that adds a capture method to the parser instance.")
+* [snapdragon-node](https://www.npmjs.com/package/snapdragon-node): Snapdragon utility for creating a new AST node in custom code, such as plugins. | [homepage](https://github.com/jonschlinkert/snapdragon-node "Snapdragon utility for creating a new AST node in custom code, such as plugins.")
+* [snapdragon-util](https://www.npmjs.com/package/snapdragon-util): Utilities for the snapdragon parser/compiler. | [homepage](https://github.com/jonschlinkert/snapdragon-util "Utilities for the snapdragon parser/compiler.")
 
 ### Contributing
 
@@ -281,19 +671,19 @@ Pull requests and stars are always welcome. For bugs and feature requests, [plea
 
 ### Contributors
 
-| **Commits** | **Contributor**<br/> | 
+| **Commits** | **Contributor** | 
 | --- | --- |
-| 106 | [jonschlinkert](https://github.com/jonschlinkert) |
+| 139 | [jonschlinkert](https://github.com/jonschlinkert) |
 | 2 | [doowb](https://github.com/doowb) |
 
 ### Building docs
 
-_(This document was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme) (a [verb](https://github.com/verbose/verb) generator), please don't edit the readme directly. Any changes to the readme must be made in [.verb.md](.verb.md).)_
+_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
 
-To generate the readme and API documentation with [verb](https://github.com/verbose/verb):
+To generate the readme, run the following command:
 
 ```sh
-$ npm install -g verb verb-generate-readme && verb
+$ npm install -g verbose/verb#dev verb-generate-readme && verb
 ```
 
 ### Running tests
@@ -301,7 +691,7 @@ $ npm install -g verb verb-generate-readme && verb
 Install dev dependencies:
 
 ```sh
-$ npm install -d && npm test
+$ npm install && npm test
 ```
 
 ### Author
@@ -309,13 +699,13 @@ $ npm install -d && npm test
 **Jon Schlinkert**
 
 * [github/jonschlinkert](https://github.com/jonschlinkert)
-* [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
+* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
 
 ### License
 
-Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert).
-Released under the [MIT license](https://github.com/jonschlinkert/snapdragon/blob/master/LICENSE).
+Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert).
+MIT
 
 ***
 
-_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.31, on October 10, 2016._
\ No newline at end of file
+_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.4.2, on March 12, 2017._
\ No newline at end of file
diff --git a/docs/compiling.md b/docs/compiling.md
new file mode 100644
index 0000000..7c7a6ee
--- /dev/null
+++ b/docs/compiling.md
@@ -0,0 +1,9 @@
+# Compiling with snapdragon
+
+<details>
+<summary><strong>Pre-requisites</strong></summary>
+If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone.
+
+To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
+</details>
+
diff --git a/docs/core-concepts.md b/docs/core-concepts.md
new file mode 100644
index 0000000..c4f9c14
--- /dev/null
+++ b/docs/core-concepts.md
@@ -0,0 +1,54 @@
+WIP (draft)
+
+# Core concepts
+
+- [Lexer](#parser)
+  * Token Stream
+  * Token
+  * Scope
+- [Parser](#parser)
+  * [Node](#node)
+  * Stack
+  * [AST](#ast)
+- [Compiler](#compiler)
+  * State
+- [Renderer](#renderer)
+  * Contexts
+  * Context
+
+## Lexer
+
+- [ ] Token
+- [ ] Tokens
+- [ ] Scope
+
+## Parser
+
+### AST
+
+TODO
+
+### Node
+
+#### Properties
+
+Officially supported properties
+
+- `type`
+- `val`
+- `nodes`
+
+**Related**
+
+- The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value.
+- The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node.
+
+## Compiler
+
+TODO
+
+## Renderer
+
+TODO
+
+[verb][]
diff --git a/docs/crash-course.md b/docs/crash-course.md
new file mode 100644
index 0000000..73d3d28
--- /dev/null
+++ b/docs/crash-course.md
@@ -0,0 +1,92 @@
+WIP (draft)
+
+## Crash course
+
+### Parser
+
+The parser's job is create an AST from a string. It does this by looping over registered parser-middleware to create nodes from captured substrings.
+
+**Parsing**
+
+When a middleware returns a node, the parser updates the string position and starts over again with the first middleware.
+
+**Parser middleware**
+
+Each parser-middleware is responsible for matching and capturing a specific "type" of substring, and optionally returning a `node` with information about what was captured.
+
+**Node**
+
+A `node` is an object that is used for storing information about a captured substring, or to mark a significant point or delimiter in the AST or string.
+
+The only required property is `node.type`.
+
+Every node has a `node.type` that
+
+semantically describes a substring that was captured by a middleware - or some other  purpose of the node, along with any other information that might be useful later during parsing or compiling.
+
+of a specific `node.type` that semantically describes the  capturing substrings
+. Matching is typically performed using a regular expression, but any means can be used.
+
+Upon capturing a substring, the parser-middleware
+
+- capturing and/or further processing relevant part(s) of the captured substring
+- returning a node with information that semantically describes the substring that was captured, along with
+
+When a parser returns a node, that indicates
+
+by calling each user-defined middleware (referred to as "parsers") until one returns a node.
+Each parser middleware
+middleware
+ a string and calling user-defined "parsers"
+
+**AST**
+
+which is an object with "nodes", where each "node" is an object with a `type`
+
+**Nodes**
+
+A `node` is an object that is used for storing and describing information about a captured substring.
+
+Every node in the AST has a `type` property, and either:
+
+- `val`: a captured substring
+- `nodes`: an array of child nodes
+
+When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will
+
+In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST.
+
+**Example**
+
+The absolute simplest AST for a single-character string might look something like this:
+
+```js
+var ast = {
+  type: 'root',
+  nodes: [
+    {
+      type: 'text',
+      val: 'a'
+    }
+  ]
+};
+```
+
+Nodes may have any additional properties, but they must have
+
+Parsers and compilers have a one-to-one relationship.
+
+The parser uses middleware for
+
+Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning.
+
+### Compiler
+
+The compiler's job is to render a string. It does this by iterating over an AST, and using the information contained in each node to determine what to render.
+
+**A compiler for every parser**
+
+Parsers and compilers have a one-to-one relationship.
+
+The parser uses middleware for
+
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..639cad3
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,28 @@
+WIP (draft)
+
+# Getting started
+
+[What is snapdragon, and who created it?](overview.html)
+
+## Table of contents
+
+- Installing snapdragon
+- Basic usage
+- Next steps
+
+## Installing snapdragon
+
+## Usage documentation
+
+**Learn how to use snapdragon**
+
+The following documentation tells you how to download and start using snapdragon. If you're intestested in creating snapdragon plugins, or you want to understand more about how snapdragon works you can find links to [developer documentation](#developer-documentation) below.
+
+is API-focused
+ how to the API methods that are
+
+## Developer documentation
+
+**Learn how to create plugins or hack on snapdragon**
+
+In the developer documentation, you will learn how Snapdragon works "under the hood" and how to create plugins. If you're more interested in test driving snapdragon,
diff --git a/docs/options.md b/docs/options.md
new file mode 100644
index 0000000..3a2ab94
--- /dev/null
+++ b/docs/options.md
@@ -0,0 +1,3 @@
+# Options
+
+WIP (draft)
diff --git a/docs/overview.md b/docs/overview.md
new file mode 100644
index 0000000..855b650
--- /dev/null
+++ b/docs/overview.md
@@ -0,0 +1,38 @@
+WIP (draft)
+
+# Overview
+
+Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome.
+
+## What is snapdragon?
+
+At its heart, snapdragon does two things:
+
+- Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST
+- Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string.
+
+**Plugins**
+
+## What can snapdragon do?
+
+You can use snapdragon to parse and convert a string into something entirely different, or use it to create "formatters" for beautifying code or plain text.
+
+**In the wild**
+
+Here's how some real projects are using snapdragon:
+
+* [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]:
+* [micromatch][]: uses snapdragon to create regex from glob patterns
+* [extglob][]: uses snapdragon to create regex from glob patterns
+* [braces][]: uses snapdragon to create regex for bash-like brace-expansion
+* [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions)
+
+## About
+
+Snapdragon was created by, [Jon Schlinkert](https://github.com/jonschlinkert), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects.
+
+If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to:
+
+- follow me on [github](jonschlinkert) for notifications and updates about my github projects
+- follow me on [twitter](jonschlinkert)
+- connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert)
diff --git a/docs/parsing.md b/docs/parsing.md
new file mode 100644
index 0000000..263cf07
--- /dev/null
+++ b/docs/parsing.md
@@ -0,0 +1,86 @@
+WIP (draft)
+
+# Parsing with snapdragon
+
+<details>
+<summary><strong>Pre-requisites</strong></summary>
+If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone.
+
+To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
+</details>
+
+<details>
+<summary><strong>Table of contents</strong></summary>
+- Usage
+- Developer
+  * Parser
+  * Parsers
+  * Custom parsers
+</details>
+
+## API
+
+## Parser
+
+The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string.
+
+To understand what `Parser` does,
+
+The snapdragon parser takes a string and creates an  by
+
+1. looping over the string
+1. invoking registered [parsers](#parsers) to create new AST nodes.
+
+The following documentation describes this in more detail.
+
+ checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and:
+  * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function)
+  * if _no matches are found_, an error is throw notifying you that the s
+
+## Parsers
+
+Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string.
+
+**How parsers work**
+
+A very basic parser function might look something like this:
+
+```js
+function() {
+  var parsed = this.parsed;
+  var pos = this.position();
+  var m = this.match(regex);
+  if (!m || !m[0]) return;
+
+  var prev = this.prev();
+  var node = pos({
+    type: type,
+    val: m[0]
+  });
+
+  define(node, 'match', m);
+  define(node, 'inside', this.stack.length > 0);
+  define(node, 'parent', prev);
+  define(node, 'parsed', parsed);
+  define(node, 'rest', this.input);
+  prev.nodes.push(node);
+}
+```
+
+TODO
+
+## Custom parsers
+
+TODO
+
+## Plugins
+
+TODO
+
+```js
+parser.use(function() {});
+```
+
+```js
+snapdragon.parser.use(function() {});
+```
diff --git a/docs/plugins.md b/docs/plugins.md
new file mode 100644
index 0000000..ffda807
--- /dev/null
+++ b/docs/plugins.md
@@ -0,0 +1,18 @@
+WIP (draft)
+
+# Snapdragon plugins
+
+```js
+var snapdragon = new Snapdgragon();
+// register plugins
+snapdragon.use(function() {});
+
+// register parser plugins
+snapdragon.parser.use(function() {});
+
+// register compiler plugins
+snapdragon.compiler.use(function() {});
+
+// parse
+var ast = snapdragon.parse('foo/bar');
+```
diff --git a/example.js b/example.js
deleted file mode 100644
index 3ed292c..0000000
--- a/example.js
+++ /dev/null
@@ -1,9 +0,0 @@
-'use strict';
-
-var Parser = require('./lib/parser');
-
-// var parser = new Parser('**/foo/*.js');
-var parser = new Parser('**/{a,b,/{c,d}}/*.js');
-var res = parser.parse();
-
-console.log(res.ast.nodes[2]);
diff --git a/examples/dot.js b/examples/dot.js
new file mode 100644
index 0000000..9e8b06c
--- /dev/null
+++ b/examples/dot.js
@@ -0,0 +1,29 @@
+var Snapdragon = require('..');
+var snapdragon = new Snapdragon();
+
+var ast = snapdragon.parser
+  .set('dot', function () {
+    var pos = this.position();
+    var m = this.match(/^\./);
+    if (!m) return;
+    return pos({
+      // define the `type` of compiler to use
+      // setting this value is optional, since the
+      // parser will add it based on the name used
+      // when registering the handler, but it's
+      // good practice since tokens aren't always
+      // returned
+      type: 'dot',
+      val: m[0]
+    })
+  })
+  .parse('.')
+
+var result = snapdragon.compiler
+  .set('dot', function (node) {
+    return this.emit('\\' + node.val);
+  })
+  .compile(ast)
+
+console.log(result.output);
+//=> '\.'
diff --git a/examples/errors.js b/examples/errors.js
new file mode 100644
index 0000000..3de8fd0
--- /dev/null
+++ b/examples/errors.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var Parser = require('../lib/parser');
+
+var parser = new Parser()
+  .set('at', function() {
+    var pos = this.position();
+    var match = this.match(/^@/);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('slash', function() {
+    var pos = this.position();
+    var match = this.match(/^\//);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('text', function() {
+    var pos = this.position();
+    var match = this.match(/^\w+/);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+
+var ast = parser.parse('git at github.com:foo/bar.git');
+console.log(ast);
diff --git a/examples/extglob.js b/examples/extglob.js
deleted file mode 100644
index 1554565..0000000
--- a/examples/extglob.js
+++ /dev/null
@@ -1,90 +0,0 @@
-'use strict';
-
-var Compiler = require('../lib/compiler');
-var Parser = require('../lib/parser');
-var parser = new Parser()
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^\\(.)/);
-    if (!m) return;
-    return pos({
-      type: 'escaped',
-      val: m[1]
-    });
-  })
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^\{/);
-    if (!m) return;
-    this.isOpen = true;
-    return pos({
-      type: 'brace.open',
-      val: m[0]
-    });
-  })
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^\}/);
-    if (!m) return;
-
-    if (!this.isOpen) {
-      throw new Error('missing opening brace');
-    }
-    this.isOpen = false;
-    return pos({
-      type: 'brace.close',
-      val: m[0]
-    });
-  })
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^,/);
-    if (!m) return;
-    return pos({
-      type: 'comma',
-      val: m[0]
-    });
-  })
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^\w+/);
-    if (!m) return;
-    return pos({
-      type: 'text',
-      val: m[0]
-    });
-  })
-  .use(function() {
-    var pos = this.position();
-    var m = this.match(/^\//);
-    if (!m) return;
-    return pos({
-      type: 'slash',
-      val: m[0]
-    });
-  });
-
-var compiler = new Compiler()
-  .set('escaped', function(node)  {
-    return this.emit('\\' + node.val, node.position);
-  })
-  .set('brace.open', function(node)  {
-    return this.emit('(?:', node.position);
-  })
-  .set('brace.close', function(node)  {
-    return this.emit(')', node.position);
-  })
-  .set('comma', function(node)  {
-    return this.emit('|', node.position);
-  })
-  .set('text', function(node)  {
-    return this.emit(node.val, node.position);
-  })
-  .set('slash', function(node)  {
-    return this.emit('/', node.position);
-  });
-
-var ast = parser.parse('a/\\{{b,c,d}/e');
-var res = compiler.compile(ast, {sourcemap: true});
-
-console.log(res);
diff --git a/examples/guide-examples.js b/examples/guide-examples.js
new file mode 100644
index 0000000..183f66c
--- /dev/null
+++ b/examples/guide-examples.js
@@ -0,0 +1,9 @@
+var Snapdragon = require('..');
+var snapdragon = new Snapdragon();
+
+/**
+ *
+ */
+
+var ast = snapdragon.parse('foo/*.js');
+console.log(ast);
diff --git a/examples/parser.js b/examples/parser.js
new file mode 100644
index 0000000..650fb29
--- /dev/null
+++ b/examples/parser.js
@@ -0,0 +1,43 @@
+'use strict';
+
+var Parser = require('../lib/parser');
+
+var parser = new Parser()
+  .set('at', function() {
+    var pos = this.position();
+    var match = this.match(/^@/);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('slash', function() {
+    var pos = this.position();
+    var match = this.match(/^\//);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('text', function() {
+    var pos = this.position();
+    var match = this.match(/^\w+/);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('dot', function() {
+    var pos = this.position();
+    var match = this.match(/^\./);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+  .set('colon', function() {
+    var pos = this.position();
+    var match = this.match(/^:/);
+    if (match) {
+      return pos({val: match[0]});
+    }
+  })
+
+var ast = parser.parse('git at github.com:foo/bar.git');
+console.log(ast);
diff --git a/examples/tiny-globs.js b/examples/tiny-globs.js
new file mode 100644
index 0000000..4d7cc47
--- /dev/null
+++ b/examples/tiny-globs.js
@@ -0,0 +1,50 @@
+'use strict';
+
+var Snapdragon = require('..');
+var Snapdragon = new Snapdragon();
+
+/**
+ * 1
+ */
+
+
+// var parser = new Parser();
+// console.log(parser.parse('foo/*.js'));
+
+
+/**
+ * 2
+ */
+
+
+snapdragon.parser
+  .set('text', function() {
+    var pos = this.position();
+    var m = this.match(/^\w+/);
+    if (m) {
+      return pos(this.node(m[0]));
+    }
+  })
+  .set('slash', function() {
+    var pos = this.position();
+    var m = this.match(/^\//);
+    if (m) {
+      return pos(this.node(m[0]));
+    }
+  })
+  .set('star', function() {
+    var pos = this.position();
+    var m = this.match(/^\*/);
+    if (m) {
+      return pos(this.node(m[0]));
+    }
+  })
+  .set('dot', function() {
+    var pos = this.position();
+    var m = this.match(/^\./);
+    if (m) {
+      return pos(this.node(m[0]));
+    }
+  });
+
+console.log(parser.parse('foo/*.js'));
diff --git a/index.js b/index.js
index 235b464..2fb2615 100644
--- a/index.js
+++ b/index.js
@@ -1,85 +1,47 @@
 'use strict';
 
-var Base = require('base');
 var define = require('define-property');
+var extend = require('extend-shallow');
 var Compiler = require('./lib/compiler');
 var Parser = require('./lib/parser');
-var utils = require('./lib/utils');
-var regexCache = {};
-var cache = {};
 
 /**
  * Create a new instance of `Snapdragon` with the given `options`.
  *
  * ```js
+ * var Snapdragon = require('snapdragon');
  * var snapdragon = new Snapdragon();
  * ```
- *
  * @param {Object} `options`
  * @api public
  */
 
 function Snapdragon(options) {
-  Base.call(this, null, options);
-  this.options = utils.extend({source: 'string'}, this.options);
-  this.compiler = new Compiler(this.options);
-  this.parser = new Parser(this.options);
-
-  Object.defineProperty(this, 'compilers', {
-    get: function() {
-      return this.compiler.compilers;
-    }
-  });
-
-  Object.defineProperty(this, 'parsers', {
-    get: function() {
-      return this.parser.parsers;
-    }
-  });
-
-  Object.defineProperty(this, 'regex', {
-    get: function() {
-      return this.parser.regex;
-    }
-  });
+  if (typeof options === 'string') {
+    var protoa = Object.create(Snapdragon.prototype);
+    Snapdragon.call(protoa);
+    return protoa.render.apply(protoa, arguments);
+  }
+
+  if (!(this instanceof Snapdragon)) {
+    var protob = Object.create(Snapdragon.prototype);
+    Snapdragon.call(protob);
+    return protob;
+  }
+
+  this.define('cache', {});
+  this.options = extend({source: 'string'}, this.options);
+  this.isSnapdragon = true;
+  this.plugins = {
+    fns: [],
+    preprocess: [],
+    visitors: {},
+    before: {},
+    after: {}
+  };
 }
 
 /**
- * Inherit Base
- */
-
-Base.extend(Snapdragon);
-
-/**
- * Add a parser to `snapdragon.parsers` for capturing the given `type` using
- * the specified regex or parser function. A function is useful if you need
- * to customize how the token is created and/or have access to the parser
- * instance to check options, etc.
- *
- * ```js
- * snapdragon
- *   .capture('slash', /^\//)
- *   .capture('dot', function() {
- *     var pos = this.position();
- *     var m = this.match(/^\./);
- *     if (!m) return;
- *     return pos({
- *       type: 'dot',
- *       val: m[0]
- *     });
- *   });
- * ```
- * @param {String} `type`
- * @param {RegExp|Function} `regex`
- * @return {Object} Returns the parser instance for chaining
- * @api public
- */
-
-Snapdragon.prototype.capture = function() {
-  return this.parser.capture.apply(this.parser, arguments);
-};
-
-/**
  * Register a plugin `fn`.
  *
  * ```js
@@ -100,14 +62,38 @@ Snapdragon.prototype.use = function(fn) {
 };
 
 /**
- * Parse the given `str`.
+ * Define a non-enumerable property or method on the Snapdragon instance.
+ * Useful in plugins for adding convenience methods that can be used in
+ * nodes.
  *
  * ```js
- * var snapdragon = new Snapdgragon([options]);
- * // register parsers
- * snapdragon.parser.use(function() {});
+ * snapdraong.define('isTypeFoo', function(node) {
+ *   return node.type === 'foo';
+ * });
+ *
+ * // inside a handler
+ * snapdragon.set('razzle-dazzle', function(node) {
+ *   if (this.isTypeFoo(node.parent)) {
+ *     // do stuff
+ *   }
+ * });
+ * ```
+ * @param {String} `name` Name of the property or method being defined
+ * @param {any} `val` Property value
+ * @return {Object} Returns the instance for chaining.
+ * @api public
+ */
+
+Snapdragon.prototype.define = function(key, val) {
+  define(this, key, val);
+  return this;
+};
+
+/**
+ * Parse the given `str` and return an AST.
  *
- * // parse
+ * ```js
+ * var snapdragon = new Snapdgragon([options]);
  * var ast = snapdragon.parse('foo/bar');
  * console.log(ast);
  * ```
@@ -118,31 +104,20 @@ Snapdragon.prototype.use = function(fn) {
  */
 
 Snapdragon.prototype.parse = function(str, options) {
-  this.options = utils.extend({}, this.options, options);
-  var parsed = this.parser.parse(str, this.options);
-
-  // add non-enumerable parser reference
-  define(parsed, 'parser', this.parser);
-  return parsed;
+  var opts = extend({}, this.options, options);
+  var ast = this.parser.parse(str, opts);
+  // add non-enumerable parser reference to AST
+  define(ast, 'parser', this.parser);
+  return ast;
 };
 
 /**
- * Compile the given `AST`.
+ * Compile an `ast` returned from `snapdragon.parse()`
  *
  * ```js
- * var snapdragon = new Snapdgragon([options]);
- * // register plugins
- * snapdragon.use(function() {});
- * // register parser plugins
- * snapdragon.parser.use(function() {});
- * // register compiler plugins
- * snapdragon.compiler.use(function() {});
- *
- * // parse
- * var ast = snapdragon.parse('foo/bar');
- *
  * // compile
  * var res = snapdragon.compile(ast);
+ * // get the compiled output string
  * console.log(res.output);
  * ```
  * @param {Object} `ast`
@@ -152,23 +127,110 @@ Snapdragon.prototype.parse = function(str, options) {
  */
 
 Snapdragon.prototype.compile = function(ast, options) {
-  this.options = utils.extend({}, this.options, options);
-  var compiled = this.compiler.compile(ast, this.options);
+  var opts = extend({}, this.options, options);
+  return this.compiler.compile(ast, opts);
+};
+
+/**
+ * Renders the given string or AST by calling `snapdragon.parse()` (if it's a string)
+ * then `snapdragon.compile()`, and returns the output string.
+ *
+ * ```js
+ * // setup parsers and compilers, then call render
+ * var str = snapdragon.render([string_or_ast]);
+ * console.log(str);
+ * ```
+ * @param {Object} `ast`
+ * @param {Object} `options`
+ * @return {Object} Returns an object with an `output` property with the rendered string.
+ * @api public
+ */
 
-  // add non-enumerable compiler reference
-  define(compiled, 'compiler', this.compiler);
-  return compiled;
+Snapdragon.prototype.render = function(ast, options) {
+  if (typeof ast === 'string') {
+    ast = this.parse(ast, options);
+  }
+  return this.compile(ast, options).output;
 };
 
 /**
- * Expose `Snapdragon`
+ * Get or set a `Snapdragon.Compiler` instance.
+ * @api public
  */
 
-module.exports = Snapdragon;
+Object.defineProperty(Snapdragon.prototype, 'compiler', {
+  configurable: true,
+  set: function(val) {
+    this.cache.compiler = val;
+  },
+  get: function() {
+    if (!this.cache.compiler) {
+      this.cache.compiler = new Compiler(this.options);
+    }
+    return this.cache.compiler;
+  }
+});
+
+/**
+ * Get or set a `Snapdragon.Parser` instance.
+ * @api public
+ */
+
+Object.defineProperty(Snapdragon.prototype, 'parser', {
+  configurable: true,
+  set: function(val) {
+    this.cache.parser = val;
+  },
+  get: function() {
+    if (!this.cache.parser) {
+      this.cache.parser = new Parser(this.options);
+    }
+    return this.cache.parser;
+  }
+});
+
+/**
+ * Get the compilers from a `Snapdragon.Compiler` instance.
+ * @api public
+ */
+
+Object.defineProperty(Snapdragon.prototype, 'compilers', {
+  get: function() {
+    return this.compiler.compilers;
+  }
+});
+
+/**
+ * Get the parsers from a `Snapdragon.Parser` instance.
+ * @api public
+ */
+
+Object.defineProperty(Snapdragon.prototype, 'parsers', {
+  get: function() {
+    return this.parser.parsers;
+  }
+});
+
+/**
+ * Get the regex cache from a `Snapdragon.Parser` instance.
+ * @api public
+ */
+
+Object.defineProperty(Snapdragon.prototype, 'regex', {
+  get: function() {
+    return this.parser.regex;
+  }
+});
 
 /**
  * Expose `Parser` and `Compiler`
  */
 
-module.exports.Compiler = Compiler;
-module.exports.Parser = Parser;
+Snapdragon.Compiler = Compiler;
+Snapdragon.Parser = Parser;
+
+/**
+ * Expose `Snapdragon`
+ */
+
+module.exports = Snapdragon;
diff --git a/lib/compiler.js b/lib/compiler.js
index 0ce9d21..b878407 100644
--- a/lib/compiler.js
+++ b/lib/compiler.js
@@ -1,27 +1,40 @@
 'use strict';
 
 var use = require('use');
+var util = require('snapdragon-util');
+var Emitter = require('component-emitter');
 var define = require('define-property');
+var extend = require('extend-shallow');
 var debug = require('debug')('snapdragon:compiler');
-var utils = require('./utils');
+var error = require('./error');
 
 /**
  * Create a new `Compiler` with the given `options`.
+ *
+ * ```js
+ * var Snapdragon = require('snapdragon');
+ * var Compiler = Snapdragon.Compiler;
+ * var compiler = new Compiler();
+ * ```
  * @param {Object} `options`
+ * @param {Object} `state` Optionally pass a "state" object to use inside visitor functions.
+ * @api public
  */
 
 function Compiler(options, state) {
   debug('initializing', __filename);
-  this.options = utils.extend({source: 'string'}, options);
+  this.options = extend({source: 'string'}, options);
+  this.emitter = new Emitter();
+  this.on = this.emitter.on.bind(this.emitter);
+  this.isCompiler = true;
   this.state = state || {};
+  this.state.inside = this.state.inside || {};
   this.compilers = {};
   this.output = '';
+  this.indent = '';
   this.set('eos', function(node) {
     return this.emit(node.val, node);
   });
-  this.set('noop', function(node) {
-    return this.emit(node.val, node);
-  });
   this.set('bos', function(node) {
     return this.emit(node.val, node);
   });
@@ -35,137 +48,221 @@ function Compiler(options, state) {
 Compiler.prototype = {
 
   /**
-   * Throw an error message with details including the cursor position.
+   * Throw a formatted error message with details including the cursor position.
+   *
+   * ```js
+   * compiler.set('foo', function(node) {
+   *   if (node.val !== 'foo') {
+   *     throw this.error('expected node.val to be "foo"', node);
+   *   }
+   * });
+   * ```
+   * @name .error
    * @param {String} `msg` Message to use in the Error.
+   * @param {Object} `node`
+   * @return {undefined}
+   * @api public
    */
 
-  error: function(msg, node) {
-    var pos = node.position || {start: {column: 0}};
-    var message = this.options.source + ' column:' + pos.start.column + ': ' + msg;
-
-    var err = new Error(message);
-    err.reason = msg;
-    err.column = pos.start.column;
-    err.source = this.pattern;
-
-    if (this.options.silent) {
-      this.errors.push(err);
-    } else {
-      throw err;
-    }
+  error: function(/*msg, node*/) {
+    return error.apply(this, arguments);
   },
 
   /**
-   * Define a non-enumberable property on the `Compiler` instance.
+   * Concat the given string to `compiler.output`.
    *
    * ```js
-   * compiler.define('foo', 'bar');
+   * compiler.set('foo', function(node) {
+   *   this.emit(node.val, node);
+   * });
    * ```
-   * @name .define
-   * @param {String} `key` propery name
-   * @param {any} `val` property value
-   * @return {Object} Returns the Compiler instance for chaining.
+   * @name .emit
+   * @param {String} `string`
+   * @param {Object} `node` Optionally pass the node to use for position if source maps are enabled.
+   * @return {String} returns the string
    * @api public
    */
 
-  define: function(key, val) {
-    define(this, key, val);
-    return this;
+  emit: function(val, node) {
+    this.output += val;
+    return val;
   },
 
   /**
-   * Emit `node.val`
+   * Emit an empty string to effectively "skip" the string for the given `node`,
+   * but still emit the position and node type.
+   *
+   * ```js
+   * // example: do nothing for beginning-of-string
+   * snapdragon.compiler.set('bos', compiler.noop);
+   * ```
+   * @name .noop
+   * @param {Object} node
+   * @api public
    */
 
-  emit: function(str, node) {
-    this.output += str;
-    return str;
+  noop: function(node) {
+    this.emit('', node);
   },
 
   /**
-   * Add a compiler `fn` with the given `name`
+   * Define a non-enumberable property on the `Compiler` instance. This is useful
+   * in plugins, for exposing methods inside handlers.
+   *
+   * ```js
+   * compiler.define('customMethod', function() {
+   *   // do stuff
+   * });
+   * ```
+   * @name .define
+   * @param {String} `key` propery name
+   * @param {any} `val` property value
+   * @return {Object} Returns the Compiler instance for chaining.
+   * @api public
    */
 
-  set: function(name, fn) {
-    this.compilers[name] = fn;
+  define: function(key, val) {
+    define(this, key, val);
     return this;
   },
 
   /**
-   * Get compiler `name`.
+   * Add a compiler `fn` for the given `type`. Compilers are called
+   * when the `.compile` method encounters a node of the given type to
+   * generate the output string.
+   *
+   * ```js
+   * compiler
+   *   .set('comma', function(node) {
+   *     this.emit(',');
+   *   })
+   *   .set('dot', function(node) {
+   *     this.emit('.');
+   *   })
+   *   .set('slash', function(node) {
+   *     this.emit('/');
+   *   });
+   * ```
+   * @name .set
+   * @param {String} `type`
+   * @param {Function} `fn`
+   * @api public
    */
 
-  get: function(name) {
-    return this.compilers[name];
+  set: function(type, fn) {
+    this.compilers[type] = fn;
+    return this;
   },
 
   /**
-   * Get the previous AST node.
+   * Get the compiler of the given `type`.
+   *
+   * ```js
+   * var fn = compiler.get('slash');
+   * ```
+   * @name .get
+   * @param {String} `type`
+   * @api public
    */
 
-  prev: function(n) {
-    return this.ast.nodes[this.idx - (n || 1)] || { type: 'bos', val: '' };
+  get: function(type) {
+    return this.compilers[type];
   },
 
   /**
-   * Get the next AST node.
+   * Visit `node` using the registered compiler function associated with the
+   * `node.type`.
+   *
+   * ```js
+   * compiler
+   *   .set('i', function(node) {
+   *     this.visit(node);
+   *   })
+   * ```
+   * @name .visit
+   * @param {Object} `node`
+   * @return {Object} returns the node
+   * @api public
    */
 
-  next: function(n) {
-    return this.ast.nodes[this.idx + (n || 1)] || { type: 'eos', val: '' };
-  },
-
-  /**
-   * Visit `node`.
-   */
+  visit: function(node) {
+    if (util.isOpen(node)) {
+      util.addType(this.state, node);
+    }
 
-  visit: function(node, nodes, i) {
-    var fn = this.compilers[node.type];
-    this.idx = i;
+    this.emitter.emit('node', node);
 
+    var fn = this.compilers[node.type] || this.compilers.unknown;
     if (typeof fn !== 'function') {
       throw this.error('compiler "' + node.type + '" is not registered', node);
     }
-    return fn.call(this, node, nodes, i);
+
+    var val = fn.call(this, node) || node;
+    if (util.isNode(val)) {
+      node = val;
+    }
+
+    if (util.isClose(node)) {
+      util.removeType(this.state, node);
+    }
+    return node;
   },
 
   /**
-   * Map visit over array of `nodes`.
+   * Iterate over `node.nodes`, calling [visit](#visit) on each node.
+   *
+   * ```js
+   * compiler
+   *   .set('i', function(node) {
+   *     utils.mapVisit(node);
+   *   })
+   * ```
+   * @name .mapVisit
+   * @param {Object} `node`
+   * @return {Object} returns the node
+   * @api public
    */
 
-  mapVisit: function(nodes) {
-    if (!Array.isArray(nodes)) {
-      throw new TypeError('expected an array');
-    }
-    var len = nodes.length;
-    var idx = -1;
-    while (++idx < len) {
-      this.visit(nodes[idx], nodes, idx);
+  mapVisit: function(parent) {
+    var nodes = parent.nodes || parent.children;
+    for (var i = 0; i < nodes.length; i++) {
+      var node = nodes[i];
+      if (!node.parent) node.parent = parent;
+      nodes[i] = this.visit(node) || node;
     }
-    return this;
   },
 
   /**
-   * Compile `ast`.
+   * Compile the given `AST` and return a string. Iterates over `ast.nodes`
+   * with [mapVisit](#mapVisit).
+   *
+   * ```js
+   * var ast = parser.parse('foo');
+   * var str = compiler.compile(ast);
+   * ```
+   * @name .compile
+   * @param {Object} `ast`
+   * @param {Object} `options` Compiler options
+   * @return {Object} returns the node
+   * @api public
    */
 
   compile: function(ast, options) {
-    var opts = utils.extend({}, this.options, options);
+    var opts = extend({}, this.options, options);
     this.ast = ast;
-    this.parsingErrors = this.ast.errors;
     this.output = '';
 
     // source map support
     if (opts.sourcemap) {
       var sourcemaps = require('./source-maps');
       sourcemaps(this);
-      this.mapVisit(this.ast.nodes);
+      this.mapVisit(this.ast);
       this.applySourceMaps();
       this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON();
-      return this;
+    } else {
+      this.mapVisit(this.ast);
     }
 
-    this.mapVisit(this.ast.nodes);
     return this;
   }
 };
diff --git a/lib/error.js b/lib/error.js
new file mode 100644
index 0000000..68a2df3
--- /dev/null
+++ b/lib/error.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var get = require('get-value');
+
+module.exports = function(msg, node) {
+  node = node || {};
+  var pos = node.position || {};
+  var line = get(node, 'position.end.line') || 1;
+  var column = get(node, 'position.end.column') || 1;
+  var source = this.options.source;
+
+  var message = source + ' <line:' + line + ' column:' + column + '>: ' + msg;
+  var err = new Error(message);
+  err.source = source;
+  err.reason = msg;
+  err.pos = pos;
+
+  if (this.options.silent) {
+    this.errors.push(err);
+  } else {
+    throw err;
+  }
+};
diff --git a/lib/parser.js b/lib/parser.js
index a5a9b31..db4e1f2 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -1,23 +1,33 @@
 'use strict';
 
 var use = require('use');
-var util = require('util');
+var util = require('snapdragon-util');
 var Cache = require('map-cache');
+var Node = require('snapdragon-node');
 var define = require('define-property');
-var debug = require('debug')('snapdragon:parser');
+var extend = require('extend-shallow');
+var Emitter = require('component-emitter');
+var isObject = require('isobject');
 var Position = require('./position');
-var utils = require('./utils');
+var error = require('./error');
 
 /**
  * Create a new `Parser` with the given `input` and `options`.
+ *
+ * ```js
+ * var Snapdragon = require('snapdragon');
+ * var Parser = Snapdragon.Parser;
+ * var parser = new Parser();
+ * ```
  * @param {String} `input`
  * @param {Object} `options`
  * @api public
  */
 
 function Parser(options) {
-  debug('initializing', __filename);
-  this.options = utils.extend({source: 'string'}, options);
+  this.options = extend({source: 'string'}, options);
+  this.isParser = true;
+  this.Node = Node;
   this.init(this.options);
   use(this);
 }
@@ -26,7 +36,7 @@ function Parser(options) {
  * Prototype methods
  */
 
-Parser.prototype = {
+Parser.prototype = Emitter({
   constructor: Parser,
 
   init: function(options) {
@@ -34,6 +44,9 @@ Parser.prototype = {
     this.input = '';
     this.parsed = '';
 
+    this.currentType = 'root';
+    this.setCount = 0;
+    this.count = 0;
     this.column = 1;
     this.line = 1;
 
@@ -43,51 +56,48 @@ Parser.prototype = {
     this.types = this.types || [];
     this.sets = this.sets || {};
     this.fns = this.fns || [];
-    this.currentType = 'root';
+    this.tokens = [];
+    this.stack = [];
 
     var pos = this.position();
-    this.bos = pos({type: 'bos', val: ''});
+    this.bos = pos(this.node({
+      type: 'bos',
+      val: ''
+    }));
 
-    this.ast = {
-      type: 'root',
-      errors: this.errors,
-      nodes: [this.bos]
-    };
+    this.ast = pos(this.node({
+      type: this.options.astType || 'root',
+      errors: this.errors
+    }));
 
-    define(this.bos, 'parent', this.ast);
+    this.ast.pushNode(this.bos);
     this.nodes = [this.ast];
-
-    this.count = 0;
-    this.setCount = 0;
-    this.stack = [];
   },
 
   /**
-   * Throw a formatted error with the cursor column and `msg`.
+   * Throw a formatted error message with details including the cursor position.
+   *
+   * ```js
+   * parser.set('foo', function(node) {
+   *   if (node.val !== 'foo') {
+   *     throw this.error('expected node.val to be "foo"', node);
+   *   }
+   * });
+   * ```
+   * @name .error
    * @param {String} `msg` Message to use in the Error.
+   * @param {Object} `node`
+   * @return {undefined}
+   * @api public
    */
 
-  error: function(msg, node) {
-    var pos = node.position || {start: {column: 0, line: 0}};
-    var line = pos.start.line;
-    var column = pos.start.column;
-    var source = this.options.source;
-
-    var message = source + ' <line:' + line + ' column:' + column + '>: ' + msg;
-    var err = new Error(message);
-    err.source = source;
-    err.reason = msg;
-    err.pos = pos;
-
-    if (this.options.silent) {
-      this.errors.push(err);
-    } else {
-      throw err;
-    }
+  error: function(/*msg, node*/) {
+    return error.apply(this, arguments);
   },
 
   /**
-   * Define a non-enumberable property on the `Parser` instance.
+   * Define a non-enumberable property on the `Parser` instance. This is useful
+   * in plugins, for exposing methods inside handlers.
    *
    * ```js
    * parser.define('foo', 'bar');
@@ -105,22 +115,68 @@ Parser.prototype = {
   },
 
   /**
+   * Create a new [Node](#node) with the given `val` and `type`.
+   *
+   * ```js
+   * parser.node('/', 'slash');
+   * ```
+   * @name .node
+   * @param {Object} `val`
+   * @param {String} `type`
+   * @return {Object} returns the [Node](#node) instance.
+   * @api public
+   */
+
+  node: function(val, type) {
+    return new this.Node(val, type);
+  },
+
+  /**
    * Mark position and patch `node.position`.
+   *
+   * ```js
+   * parser.set('foo', function(node) {
+   *   var pos = this.position();
+   *   var match = this.match(/foo/);
+   *   if (match) {
+   *     // call `pos` with the node
+   *     return pos(this.node(match[0]));
+   *   }
+   * });
+   * ```
+   * @name .position
+   * @return {Function} Returns a function that takes a `node`
+   * @api public
    */
 
   position: function() {
     var start = { line: this.line, column: this.column };
+    var parsed = this.parsed;
     var self = this;
 
     return function(node) {
-      define(node, 'position', new Position(start, self));
+      if (!node.isNode) node = new Node(node);
+      node.define('position', new Position(start, self));
+      node.define('parsed', parsed);
+      node.define('inside', self.stack.length > 0);
+      node.define('rest', self.input);
       return node;
     };
   },
 
   /**
-   * Set parser `name` with the given `fn`
-   * @param {String} `name`
+   * Add parser `type` with the given visitor `fn`.
+   *
+   * ```js
+   *  parser.set('all', function() {
+   *    var match = this.match(/^./);
+   *    if (match) {
+   *      return this.node(match[0]);
+   *    }
+   *  });
+   * ```
+   * @name .set
+   * @param {String} `type`
    * @param {Function} `fn`
    * @api public
    */
@@ -134,18 +190,34 @@ Parser.prototype = {
   },
 
   /**
-   * Get parser `name`
-   * @param {String} `name`
+   * Get parser `type`.
+   *
+   * ```js
+   * var fn = parser.get('slash');
+   * ```
+   * @name .get
+   * @param {String} `type`
    * @api public
    */
 
-  get: function(name) {
-    return this.parsers[name];
+  get: function(type) {
+    return this.parsers[type];
   },
 
   /**
-   * Push a `token` onto the `type` stack.
+   * Push a node onto the stack for the given `type`.
    *
+   * ```js
+   * parser.set('all', function() {
+   *   var match = this.match(/^./);
+   *   if (match) {
+   *     var node = this.node(match[0]);
+   *     this.push(node);
+   *     return node;
+   *   }
+   * });
+   * ```
+   * @name .push
    * @param {String} `type`
    * @return {Object} `token`
    * @api public
@@ -159,7 +231,23 @@ Parser.prototype = {
   },
 
   /**
-   * Pop a token off of the `type` stack
+   * Pop a token off of the stack of the given `type`.
+   *
+   * ```js
+   * parser.set('close', function() {
+   *   var match = this.match(/^\}/);
+   *   if (match) {
+   *     var node = this.node({
+   *       type: 'close',
+   *       val: match[0]
+   *     });
+   *
+   *     this.pop(node.type);
+   *     return node;
+   *   }
+   * });
+   * ```
+   * @name .pop
    * @param {String} `type`
    * @returns {Object} Returns a token
    * @api public
@@ -173,16 +261,30 @@ Parser.prototype = {
   },
 
   /**
-   * Return true if inside a `stack` node. Types are `braces`, `parens` or `brackets`.
+   * Return true if inside a "set" of the given `type`. Sets are created
+   * manually by adding a type to `parser.sets`. A node is "inside" a set
+   * when an `*.open` node for the given `type` was previously pushed onto the set.
+   * The type is removed from the set by popping it off when the `*.close`
+   * node for the given type is reached.
    *
+   * ```js
+   * parser.set('close', function() {
+   *   var pos = this.position();
+   *   var m = this.match(/^\}/);
+   *   if (!m) return;
+   *   if (!this.isInside('bracket')) {
+   *     throw new Error('missing opening bracket');
+   *   }
+   * });
+   * ```
+   * @name .isInside
    * @param {String} `type`
    * @return {Boolean}
    * @api public
    */
 
   isInside: function(type) {
-    this.sets[type] = this.sets[type] || [];
-    return this.sets[type].length > 0;
+    return Array.isArray(this.sets[type]) && this.sets[type].length > 0;
   },
 
   /**
@@ -191,6 +293,7 @@ Parser.prototype = {
    * ```js
    * parser.isType(node, 'brace');
    * ```
+   * @name .isType
    * @param {Object} `node`
    * @param {String} `type`
    * @return {Boolean}
@@ -202,14 +305,21 @@ Parser.prototype = {
   },
 
   /**
-   * Get the previous AST node
+   * Get the previous AST node from the `parser.stack` (when inside a nested
+   * context) or `parser.nodes`.
+   *
+   * ```js
+   * var prev = this.prev();
+   * ```
+   * @name .prev
    * @return {Object}
+   * @api public
    */
 
   prev: function(n) {
     return this.stack.length > 0
-      ? utils.last(this.stack, n)
-      : utils.last(this.nodes, n);
+      ? util.last(this.stack, n)
+      : util.last(this.nodes, n);
   },
 
   /**
@@ -221,6 +331,20 @@ Parser.prototype = {
   },
 
   /**
+   * Returns the string up to the given `substring`,
+   * if it exists, and advances the cursor position past the substring.
+   */
+
+  advanceTo: function(str) {
+    var idx = this.input.indexOf(str);
+    if (idx !== -1) {
+      var val = this.input.slice(0, idx);
+      this.consume(idx + str.length);
+      return val;
+    }
+  },
+
+  /**
    * Update column based on `str`.
    */
 
@@ -235,8 +359,15 @@ Parser.prototype = {
 
   /**
    * Match `regex`, return captures, and update the cursor position by `match[0]` length.
+   *
+   * ```js
+   * // make sure to use the starting regex boundary: "^"
+   * var match = this.match(/^\./);
+   * ```
+   * @name .prev
    * @param {RegExp} `regex`
    * @return {Object}
+   * @api public
    */
 
   match: function(regex) {
@@ -248,131 +379,18 @@ Parser.prototype = {
   },
 
   /**
-   * Capture `type` with the given regex.
-   * @param {String} `type`
-   * @param {RegExp} `regex`
-   * @return {Function}
-   */
-
-  capture: function(type, regex) {
-    if (typeof regex === 'function') {
-      return this.set.apply(this, arguments);
-    }
-
-    this.regex.set(type, regex);
-    this.set(type, function() {
-      var parsed = this.parsed;
-      var pos = this.position();
-      var m = this.match(regex);
-      if (!m || !m[0]) return;
-
-      var prev = this.prev();
-      var node = pos({
-        type: type,
-        val: m[0],
-        parsed: parsed,
-        rest: this.input
-      });
-
-      if (m[1]) {
-        node.inner = m[1];
-      }
-
-      define(node, 'inside', this.stack.length > 0);
-      define(node, 'parent', prev);
-      prev.nodes.push(node);
-    }.bind(this));
-    return this;
-  },
-
-  /**
-   * Create a parser with open and close for parens,
-   * brackets or braces
+   * Push `node` to `parent.nodes` and assign `node.parent`
    */
 
-  capturePair: function(type, openRegex, closeRegex, fn) {
-    this.sets[type] = this.sets[type] || [];
-
-    /**
-     * Open
-     */
-
-    this.set(type + '.open', function() {
-      var parsed = this.parsed;
-      var pos = this.position();
-      var m = this.match(openRegex);
-      if (!m || !m[0]) return;
-
-      var val = m[0];
-      this.setCount++;
-      this.specialChars = true;
-      var open = pos({
-        type: type + '.open',
-        val: val,
-        rest: this.input
-      });
-
-      if (typeof m[1] !== 'undefined') {
-        open.inner = m[1];
-      }
-
-      var prev = this.prev();
-      var node = pos({
-        type: type,
-        nodes: [open]
-      });
-
-      define(node, 'rest', this.input);
-      define(node, 'parsed', parsed);
-      define(node, 'prefix', m[1]);
-      define(node, 'parent', prev);
-      define(open, 'parent', node);
-
-      if (typeof fn === 'function') {
-        fn.call(this, open, node);
-      }
-
-      this.push(type, node);
-      prev.nodes.push(node);
-    });
-
-    /**
-     * Close
-     */
-
-    this.set(type + '.close', function() {
-      var pos = this.position();
-      var m = this.match(closeRegex);
-      if (!m || !m[0]) return;
-
-      var parent = this.pop(type);
-      var node = pos({
-        type: type + '.close',
-        rest: this.input,
-        suffix: m[1],
-        val: m[0]
-      });
-
-      if (!this.isType(parent, type)) {
-        if (this.options.strict) {
-          throw new Error('missing opening "' + type + '"');
-        }
-
-        this.setCount--;
-        node.escaped = true;
-        return node;
-      }
-
-      if (node.suffix === '\\') {
-        parent.escaped = true;
-        node.escaped = true;
-      }
-
-      parent.nodes.push(node);
+  pushNode: function(node, parent) {
+    if (node && parent) {
+      if (parent === node) parent = this.ast;
       define(node, 'parent', parent);
-    });
-
-    return this;
+      if (parent.nodes) parent.nodes.push(node);
+      if (this.sets.hasOwnProperty(parent.type)) {
+        this.currentType = parent.type;
+      }
+    }
   },
 
   /**
@@ -380,62 +398,100 @@ Parser.prototype = {
    */
 
   eos: function() {
-    var pos = this.position();
     if (this.input) return;
+    var pos = this.position();
     var prev = this.prev();
 
     while (prev.type !== 'root' && !prev.visited) {
       if (this.options.strict === true) {
-        throw new SyntaxError('invalid syntax:' + util.inspect(prev, null, 2));
+        throw new SyntaxError('invalid syntax:' + prev);
       }
 
-      if (!hasDelims(prev)) {
-        prev.parent.escaped = true;
-        prev.escaped = true;
+      if (!util.hasOpenAndClose(prev)) {
+        define(prev.parent, 'escaped', true);
+        define(prev, 'escaped', true);
       }
 
-      visit(prev, function(node) {
-        if (!hasDelims(node.parent)) {
-          node.parent.escaped = true;
-          node.escaped = true;
+      this.visit(prev, function(node) {
+        if (!util.hasOpenAndClose(node.parent)) {
+          define(node.parent, 'escaped', true);
+          define(node, 'escaped', true);
         }
       });
 
       prev = prev.parent;
     }
 
-    var tok = pos({
-      type: 'eos',
-      val: this.append || ''
-    });
+    var node = pos(this.node(this.append || '', 'eos'));
+    if (typeof this.options.eos === 'function') {
+      node = this.options.eos.call(this, node);
+    }
+
+    if (this.parsers.eos) {
+      this.parsers.eos.call(this, node);
+    }
 
-    define(tok, 'parent', this.ast);
-    return tok;
+    define(node, 'parent', this.ast);
+    return node;
   },
 
   /**
    * Run parsers to advance the cursor position
    */
 
-  next: function() {
+  getNext: function() {
     var parsed = this.parsed;
     var len = this.types.length;
     var idx = -1;
-    var tok;
 
     while (++idx < len) {
-      if ((tok = this.parsers[this.types[idx]].call(this))) {
+      var type = this.types[idx];
+      var tok = this.parsers[type].call(this);
+      if (tok === true) {
+        break;
+      }
+
+      if (tok) {
+        tok.type = tok.type || type;
         define(tok, 'rest', this.input);
         define(tok, 'parsed', parsed);
         this.last = tok;
+        this.tokens.push(tok);
+        this.emit('node', tok);
         return tok;
       }
     }
   },
 
   /**
-   * Parse the given string.
-   * @return {Array}
+   * Run parsers to get the next AST node
+   */
+
+  advance: function() {
+    var input = this.input;
+    this.pushNode(this.getNext(), this.prev());
+
+    // if we're here and input wasn't modified, throw an error
+    if (this.input && input === this.input) {
+      var chokedOn = this.input.slice(0, 10);
+      var err = this.error('no parser for: "' + chokedOn, this.last);
+      if (this.hasListeners('error')) {
+        this.emit('error', err);
+      } else {
+        throw err;
+      }
+    }
+  },
+
+  /**
+   * Parse the given string an return an AST object.
+   *
+   * ```js
+   * var ast = parser.parse('foo/bar');
+   * ```
+   * @param {String} `input`
+   * @return {Object} Returns an AST with `ast.nodes`
+   * @api public
    */
 
   parse: function(input) {
@@ -446,84 +502,58 @@ Parser.prototype = {
     this.init(this.options);
     this.orig = input;
     this.input = input;
-    var self = this;
 
-    function parse() {
-      // check input before calling `.next()`
-      input = self.input;
-
-      // get the next AST ndoe
-      var node = self.next();
-      if (node) {
-        var prev = self.prev();
-        if (prev) {
-          define(node, 'parent', prev);
-          if (prev.nodes) {
-            prev.nodes.push(node);
-          }
-        }
+    // run parsers
+    while (this.input) this.advance();
 
-        if (self.sets.hasOwnProperty(prev.type)) {
-          self.currentType = prev.type;
-        }
-      }
-
-      // if we got here but input is not changed, throw an error
-      if (self.input && input === self.input) {
-        throw new Error('no parsers registered for: "' + self.input.slice(0, 5) + '"');
-      }
-    }
-
-    while (this.input) parse();
-    if (this.stack.length && this.options.strict) {
-      var node = this.stack.pop();
-      throw this.error('missing opening ' + node.type + ': "' + this.orig + '"');
-    }
+    // balance unmatched sets, if not disabled
+    balanceSets(this, this.stack.pop());
 
+    // create end-of-string node
     var eos = this.eos();
-    var tok = this.prev();
-    if (tok.type !== 'eos') {
-      this.ast.nodes.push(eos);
+    var ast = this.prev();
+    if (ast.type === 'root') {
+      this.pushNode(eos, ast);
     }
-
     return this.ast;
-  }
-};
+  },
 
-/**
- * Visit `node` with the given `fn`
- */
+  /**
+   * Visit `node` with the given `fn`
+   */
 
-function visit(node, fn) {
-  if (!node.visited) {
-    define(node, 'visited', true);
-    return node.nodes ? mapVisit(node.nodes, fn) : fn(node);
-  }
-  return node;
-}
+  visit: function(node, fn) {
+    if (!isObject(node) || node.isNode !== true) {
+      throw new Error('expected node to be an instance of Node');
+    }
+    if (node.visited) return;
+    node.define('visited', true);
+    node = fn(node) || node;
+    if (node.nodes) {
+      this.mapVisit(node.nodes, fn, node);
+    }
+    return node;
+  },
 
-/**
- * Map visit over array of `nodes`.
- */
+  /**
+   * Map visit over array of `nodes`.
+   */
 
-function mapVisit(nodes, fn) {
-  var len = nodes.length;
-  var idx = -1;
-  while (++idx < len) {
-    visit(nodes[idx], fn);
+  mapVisit: function(nodes, fn, parent) {
+    for (var i = 0; i < nodes.length; i++) {
+      this.visit(nodes[i], fn);
+    }
   }
-}
-
-function hasOpen(node) {
-  return node.nodes && node.nodes[0].type === (node.type + '.open');
-}
+});
 
-function hasClose(node) {
-  return node.nodes && utils.last(node.nodes).type === (node.type + '.close');
-}
-
-function hasDelims(node) {
-  return hasOpen(node) && hasClose(node);
+function balanceSets(parser, node) {
+  if (node && parser.options.strict === true) {
+    throw parser.error('imbalanced "' + node.type + '": "' + parser.orig + '"');
+  }
+  if (node && node.nodes && node.nodes.length) {
+    var first = node.nodes[0];
+    first.val = '\\' + first.val;
+  }
 }
 
 /**
diff --git a/lib/position.js b/lib/position.js
index c859696..ce3e5a9 100644
--- a/lib/position.js
+++ b/lib/position.js
@@ -1,14 +1,10 @@
 'use strict';
 
-var define = require('define-property');
-
 /**
- * Store position for a node
+ * Store the position for a node
  */
 
 module.exports = function Position(start, parser) {
   this.start = start;
   this.end = { line: parser.line, column: parser.column };
-  define(this, 'content', parser.orig);
-  define(this, 'source', parser.options.source);
 };
diff --git a/lib/source-maps.js b/lib/source-maps.js
index d8e638b..e476d56 100644
--- a/lib/source-maps.js
+++ b/lib/source-maps.js
@@ -3,7 +3,8 @@
 var fs = require('fs');
 var path = require('path');
 var define = require('define-property');
-var utils = require('./utils');
+var sourceMapResolve = require('source-map-resolve');
+var SourceMap = require('source-map');
 
 /**
  * Expose `mixin()`.
@@ -23,7 +24,7 @@ module.exports = mixin;
 
 function mixin(compiler) {
   define(compiler, '_comment', compiler.comment);
-  compiler.map = new utils.SourceMap.SourceMapGenerator();
+  compiler.map = new SourceMap.SourceMapGenerator();
   compiler.position = { line: 1, column: 1 };
   compiler.content = {};
   compiler.files = {};
@@ -59,7 +60,7 @@ exports.emit = function(str, node) {
   var source = position.source;
   if (source) {
     if (position.filepath) {
-      source = utils.unixify(position.filepath);
+      source = unixify(position.filepath);
     }
 
     this.map.addMapping({
@@ -80,10 +81,10 @@ exports.emit = function(str, node) {
     if (position.filepath) {
       this.addFile(source, position);
     }
-
-    this.updatePosition(str);
-    this.output += str;
   }
+
+  this.updatePosition(str);
+  this.output += str;
   return str;
 };
 
@@ -122,11 +123,11 @@ exports.applySourceMaps = function() {
     this.map.setSourceContent(file, content);
 
     if (this.options.inputSourcemaps === true) {
-      var originalMap = utils.sourceMapResolve.resolveSync(content, file, fs.readFileSync);
+      var originalMap = sourceMapResolve.resolveSync(content, file, fs.readFileSync);
       if (originalMap) {
-        var map = new utils.SourceMap.SourceMapConsumer(originalMap.map);
+        var map = new SourceMap.SourceMapConsumer(originalMap.map);
         var relativeTo = originalMap.sourcesRelativeTo;
-        this.map.applySourceMap(map, file, utils.unixify(path.dirname(relativeTo)));
+        this.map.applySourceMap(map, file, unixify(path.dirname(relativeTo)));
       }
     }
   }, this);
@@ -143,3 +144,11 @@ exports.comment = function(node) {
   }
   return this._comment(node);
 };
+
+/**
+ * Convert backslash in the given string to forward slashes
+ */
+
+function unixify(fp) {
+  return fp.split(/\\+/).join('/');
+}
diff --git a/lib/utils.js b/lib/utils.js
deleted file mode 100644
index 33f07e1..0000000
--- a/lib/utils.js
+++ /dev/null
@@ -1,48 +0,0 @@
-'use strict';
-
-/**
- * Module dependencies
- */
-
-exports.extend = require('extend-shallow');
-exports.SourceMap = require('source-map');
-exports.sourceMapResolve = require('source-map-resolve');
-
-/**
- * Convert backslash in the given string to forward slashes
- */
-
-exports.unixify = function(fp) {
-  return fp.split(/\\+/).join('/');
-};
-
-/**
- * Return true if `val` is a non-empty string
- *
- * @param {String} `str`
- * @return {Boolean}
- */
-
-exports.isString = function(str) {
-  return str && typeof str === 'string';
-};
-
-/**
- * Cast `val` to an array
- * @return {Array}
- */
-
-exports.arrayify = function(val) {
-  if (typeof val === 'string') return [val];
-  return val ? (Array.isArray(val) ? val : [val]) : [];
-};
-
-/**
- * Get the last `n` element from the given `array`
- * @param {Array} `array`
- * @return {*}
- */
-
-exports.last = function(arr, n) {
-  return arr[arr.length - (n || 1)];
-};
diff --git a/package.json b/package.json
index f68bf3f..7c68297 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
 {
   "name": "snapdragon",
-  "description": "Fast, pluggable and easy-to-use parser-renderer factory.",
-  "version": "0.8.1",
+  "description": "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.",
+  "version": "0.11.0",
   "homepage": "https://github.com/jonschlinkert/snapdragon",
   "author": "Jon Schlinkert (https://github.com/jonschlinkert)",
   "contributors": [
-    "Brian Woodward <brian.woodward at gmail.com> (https://github.com/doowb)",
+    "Brian Woodward <brian.woodward at gmail.com> (https://twitter.com/doowb)",
     "Jon Schlinkert <jon.schlinkert at sellside.com> (http://twitter.com/jonschlinkert)"
   ],
   "repository": "jonschlinkert/snapdragon",
@@ -15,7 +15,8 @@
   "license": "MIT",
   "files": [
     "index.js",
-    "lib"
+    "lib",
+    "verbfile.js"
   ],
   "main": "index.js",
   "engines": {
@@ -25,30 +26,37 @@
     "test": "mocha"
   },
   "dependencies": {
-    "base": "^0.11.1",
-    "debug": "^2.2.0",
+    "component-emitter": "^1.2.1",
+    "debug": "^2.6.2",
     "define-property": "^0.2.5",
     "extend-shallow": "^2.0.1",
+    "get-value": "^2.0.6",
+    "isobject": "^3.0.0",
     "map-cache": "^0.2.2",
+    "snapdragon-node": "^1.0.6",
+    "snapdragon-util": "^2.1.1",
     "source-map": "^0.5.6",
     "source-map-resolve": "^0.5.0",
-    "use": "^2.0.0"
+    "use": "^2.0.2"
   },
   "devDependencies": {
     "gulp": "^3.9.1",
     "gulp-eslint": "^3.0.1",
-    "gulp-format-md": "^0.1.10",
+    "gulp-format-md": "^0.1.11",
     "gulp-istanbul": "^1.1.1",
     "gulp-mocha": "^3.0.1",
-    "gulp-unused": "^0.2.0",
-    "mocha": "^3.0.2"
+    "gulp-unused": "^0.2.1",
+    "mocha": "^3.2.0",
+    "snapdragon-capture": "^0.2.0",
+    "snapdragon-capture-set": "^1.0.1",
+    "verb-generate-readme": "^0.4.3"
   },
   "keywords": [
     "lexer",
     "snapdragon"
   ],
   "verb": {
-    "toc": false,
+    "toc": "collapsible",
     "layout": "default",
     "tasks": [
       "readme"
@@ -57,22 +65,33 @@
       "gulp-format-md"
     ],
     "related": {
-      "description": "These libraries use snapdragon:",
-      "list": [
+      "description": "A few of the libraries that use snapdragon:",
+      "implementations": [
         "braces",
-        "micromatch",
+        "breakdance",
         "expand-brackets",
-        "extglob"
+        "extglob",
+        "micromatch",
+        "nanomatch"
+      ],
+      "list": [
+        "snapdragon-capture",
+        "snapdragon-capture-set",
+        "snapdragon-node",
+        "snapdragon-util"
       ]
     },
+    "lint": {
+      "reflinks": true
+    },
     "reflinks": [
       "css",
       "pug",
+      "snapdragon-capture",
+      "snapdragon-capture-set",
+      "snapdragon-node",
       "verb",
       "verb-generate-readme"
-    ],
-    "lint": {
-      "reflinks": true
-    }
+    ]
   }
 }
diff --git a/support/src/content/compiling.md b/support/src/content/compiling.md
new file mode 100644
index 0000000..7c7a6ee
--- /dev/null
+++ b/support/src/content/compiling.md
@@ -0,0 +1,9 @@
+# Compiling with snapdragon
+
+<details>
+<summary><strong>Pre-requisites</strong></summary>
+If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone.
+
+To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
+</details>
+
diff --git a/support/src/content/core-concepts.md b/support/src/content/core-concepts.md
new file mode 100644
index 0000000..778c303
--- /dev/null
+++ b/support/src/content/core-concepts.md
@@ -0,0 +1,56 @@
+WIP (draft)
+
+# Core concepts
+
+- [Lexer](#parser)
+  * Token Stream
+  * Token
+  * Scope
+- [Parser](#parser)
+  * [Node](#node)
+  * Stack
+  * [AST](#ast)
+- [Compiler](#compiler)
+  * State
+- [Renderer](#renderer)
+  * Contexts
+  * Context
+
+## Lexer
+
+- [ ] Token
+- [ ] Tokens
+- [ ] Scope
+
+## Parser
+
+### AST
+
+TODO
+
+### Node
+
+#### Properties
+
+Officially supported properties
+
+- `type`
+- `val`
+- `nodes`
+
+**Related**
+
+- The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value.
+- The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node.
+
+
+## Compiler
+
+TODO
+
+## Renderer
+
+TODO
+
+
+[verb][]
diff --git a/support/src/content/crash-course.md b/support/src/content/crash-course.md
new file mode 100644
index 0000000..9376c77
--- /dev/null
+++ b/support/src/content/crash-course.md
@@ -0,0 +1,95 @@
+WIP (draft)
+
+## Crash course
+
+### Parser
+
+The parser's job is create an AST from a string. It does this by looping over registered parser-middleware to create nodes from captured substrings.
+
+**Parsing**
+
+When a middleware returns a node, the parser updates the string position and starts over again with the first middleware.
+
+**Parser middleware**
+
+Each parser-middleware is responsible for matching and capturing a specific "type" of substring, and optionally returning a `node` with information about what was captured.
+
+
+**Node**
+
+A `node` is an object that is used for storing information about a captured substring, or to mark a significant point or delimiter in the AST or string.
+
+The only required property is `node.type`.
+
+Every node has a `node.type` that
+
+semantically describes a substring that was captured by a middleware - or some other  purpose of the node, along with any other information that might be useful later during parsing or compiling.
+
+
+of a specific `node.type` that semantically describes the  capturing substrings
+. Matching is typically performed using a regular expression, but any means can be used.
+
+Upon capturing a substring, the parser-middleware
+
+- capturing and/or further processing relevant part(s) of the captured substring
+- returning a node with information that semantically describes the substring that was captured, along with
+
+When a parser returns a node, that indicates
+
+by calling each user-defined middleware (referred to as "parsers") until one returns a node.
+Each parser middleware
+middleware
+ a string and calling user-defined "parsers"
+
+**AST**
+
+which is an object with "nodes", where each "node" is an object with a `type`
+
+**Nodes**
+
+A `node` is an object that is used for storing and describing information about a captured substring.
+
+Every node in the AST has a `type` property, and either:
+
+- `val`: a captured substring
+- `nodes`: an array of child nodes
+
+When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will
+
+In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST.
+
+**Example**
+
+The absolute simplest AST for a single-character string might look something like this:
+
+```js
+var ast = {
+  type: 'root',
+  nodes: [
+    {
+      type: 'text',
+      val: 'a'
+    }
+  ]
+};
+```
+
+Nodes may have any additional properties, but they must have
+
+Parsers and compilers have a one-to-one relationship.
+
+The parser uses middleware for
+
+
+Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning.
+
+### Compiler
+
+The compiler's job is to render a string. It does this by iterating over an AST, and using the information contained in each node to determine what to render.
+
+**A compiler for every parser**
+
+Parsers and compilers have a one-to-one relationship.
+
+The parser uses middleware for
+
diff --git a/support/src/content/getting-started.md b/support/src/content/getting-started.md
new file mode 100644
index 0000000..d658782
--- /dev/null
+++ b/support/src/content/getting-started.md
@@ -0,0 +1,29 @@
+WIP (draft)
+
+# Getting started
+
+[What is snapdragon, and who created it?](overview.html)
+
+## Table of contents
+
+- Installing snapdragon
+- Basic usage
+- Next steps
+
+
+## Installing snapdragon
+
+## Usage documentation
+
+**Learn how to use snapdragon**
+
+The following documentation tells you how to download and start using snapdragon. If you're intestested in creating snapdragon plugins, or you want to understand more about how snapdragon works you can find links to [developer documentation](#developer-documentation) below.
+
+is API-focused
+ how to the API methods that are
+
+## Developer documentation
+
+**Learn how to create plugins or hack on snapdragon**
+
+In the developer documentation, you will learn how Snapdragon works "under the hood" and how to create plugins. If you're more interested in test driving snapdragon,
diff --git a/support/src/content/guides/creating-your-first-parser.md b/support/src/content/guides/creating-your-first-parser.md
new file mode 100644
index 0000000..1453abb
--- /dev/null
+++ b/support/src/content/guides/creating-your-first-parser.md
@@ -0,0 +1,124 @@
+---
+title: Creating your first Snapdragon parser
+---
+
+This guide will show you how to create a basic parser by starting off with the string we want to parse, and gradually adding the code we need based on feedback from Snapdragon.
+
+Let's go!
+
+## Prerequisites
+
+Before we dive in, let's make sure you have snapdragon installed and setup properly.
+
+### Install snapdragon
+
+You can use either [npm](https://npmjs.com) or [yarn](https://yarnpkg.com/) to install snapdragon:
+
+**Install with NPM**
+
+```sh
+$ npm install snapdragon
+```
+
+**Install with yarn**
+
+```sh
+$ yarn add snapdragon
+```
+
+### Setup snapdragon
+
+Create a file in the current working directory named `parser.js` (or whatever you prefer), and add the following code:
+
+```js
+// add snapdragon using node's "require()"
+var Snapdragon = require('snapdragon');
+
+// create an instance of Snapdragon. This is the basis for your very own application.
+var snapdragon = new Snapdragon();
+```
+
+With that out of the way, let's get started on our parser!
+
+## Parsing strategy
+
+Feel free to skip this section and jump [straight to the code](#learning-by-doing), or follow along as we discuss our high-level parser strategy and goals.
+
+### Defining success
+
+The purpose of this guide isn't to parse something complicated or super-interesting. It's to show you how the parser works. If we accomplish that, then you're only limited by your imagination!
+
+**The goal**
+
+The string we're going to parse is: `foo/*.js` (a basic glob pattern).
+
+_(sidebar: whilst there are numerous approaches one could take to parsing or tokenizing any string, and there are many other factors that would need to be considered, such as escaping, user-defined options, and so on, we're going to keep this simple for illustrative purposes, thus these things fall outside of the scope of this guide)_
+
+It's always good to have a basic parsing strategy before you start. As it relates to glob patterns, our high level strategy might be something like: "I want my parser to be able to differentiate between wildcards (stars in this case), slashes, and non-wildcard strings".
+
+Our parser will be considered "successful" once it is able to do these things.
+
+### Begin with the end in mind
+
+**The expected result**
+
+Our final AST will be an object with "nodes", where each "node" is an object with a `type` that semantically describes a substring that was captured by the parser.
+
+Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning.
+
+## Learning by doing
+
+Okay, it's time to start writing code. To parse `foo/*.js` we'll need to figure out how to capture each "type" of substring.
+
+Although we won't be doing any compiling in this guide, it will help to understand the role the compiler plays, so that you can factor that into your decisions with the parser.
+
+**For every node type, there is a parser and a compiler**
+
+
+
+
+
+The actual approach you use for determining where one substring ends and another begins can be a combination of regex, string position/index, or any other mechanism available to you in javascript. Whatever approach you take, Snapdragon's job is to make it as easy as possible for for you.
+
+**
+
+
+
+ node `type` Snapdragon uses "parsers" are the middleware that  to capture substrings. This is what we're going to create next.
+
+
+But instead of thinking about code and what to capture, let's try a different approach and take advantage of snapdragon's error reporting to figure out the next step.
+
+Update `parser.js` with the following code:
+
+```js
+var Snapdragon = require('snapdragon');
+var snapdragon = new Snapdragon();
+
+/**
+ * <!-- leave some space here... we'll be adding code to make our parser work -->
+ */
+
+var ast = snapdragon.parse('foo/*.js');
+console.log(ast);
+```
+
+Then run the following command:
+
+```sh
+$ node parser.js
+```
+
+You should see an error message that looks something like the following:
+
+```console
+Error: string <line:1 column:1>: no parser for: "foo/*.js
+```
+
+There are a few important bits of information in this message:
+
+- `line:1 column: 1` tells us where in the input string this is happening. It's no suprise that we're getting an error on the very first character of our string.
+- `no parser for:` tells us that no "parsers" are registered for the substring that follows in the error message.
+
+
+###
diff --git a/support/src/content/options.md b/support/src/content/options.md
new file mode 100644
index 0000000..3a2ab94
--- /dev/null
+++ b/support/src/content/options.md
@@ -0,0 +1,3 @@
+# Options
+
+WIP (draft)
diff --git a/support/src/content/overview.md b/support/src/content/overview.md
new file mode 100644
index 0000000..f0f0a4a
--- /dev/null
+++ b/support/src/content/overview.md
@@ -0,0 +1,40 @@
+WIP (draft)
+
+# Overview
+
+Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome.
+
+## What is snapdragon?
+
+At its heart, snapdragon does two things:
+
+- Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST
+- Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string.
+
+**Plugins**
+
+
+
+## What can snapdragon do?
+
+You can use snapdragon to parse and convert a string into something entirely different, or use it to create "formatters" for beautifying code or plain text.
+
+**In the wild**
+
+Here's how some real projects are using snapdragon:
+
+* [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]:
+* [micromatch][]: uses snapdragon to create regex from glob patterns
+* [extglob][]: uses snapdragon to create regex from glob patterns
+* [braces][]: uses snapdragon to create regex for bash-like brace-expansion
+* [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions)
+
+## About
+
+Snapdragon was created by, [Jon Schlinkert]({%= author.url %}), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects.
+
+If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to:
+
+- follow me on [github]({%= author.twitter %}) for notifications and updates about my github projects
+- follow me on [twitter]({%= author.twitter %})
+- connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert)
diff --git a/support/src/content/parsing.md b/support/src/content/parsing.md
new file mode 100644
index 0000000..668f205
--- /dev/null
+++ b/support/src/content/parsing.md
@@ -0,0 +1,92 @@
+WIP (draft)
+
+# Parsing with snapdragon
+
+<details>
+<summary><strong>Pre-requisites</strong></summary>
+If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone.
+
+To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
+</details>
+
+
+<details>
+<summary><strong>Table of contents</strong></summary>
+- Usage
+- Developer
+  * Parser
+  * Parsers
+  * Custom parsers
+</details>
+
+## API
+
+## Parser
+
+The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string.
+
+To understand what `Parser` does,
+
+The snapdragon parser takes a string and creates an  by
+
+1. looping over the string
+1. invoking registered [parsers](#parsers) to create new AST nodes.
+
+The following documentation describes this in more detail.
+
+
+ checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and:
+  * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function)
+  * if _no matches are found_, an error is throw notifying you that the s
+
+
+## Parsers
+
+Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string.
+
+**How parsers work**
+
+A very basic parser function might look something like this:
+
+```js
+function() {
+  var parsed = this.parsed;
+  var pos = this.position();
+  var m = this.match(regex);
+  if (!m || !m[0]) return;
+
+  var prev = this.prev();
+  var node = pos({
+    type: type,
+    val: m[0]
+  });
+
+  define(node, 'match', m);
+  define(node, 'inside', this.stack.length > 0);
+  define(node, 'parent', prev);
+  define(node, 'parsed', parsed);
+  define(node, 'rest', this.input);
+  prev.nodes.push(node);
+}
+```
+
+TODO
+
+
+## Custom parsers
+
+TODO
+
+## Plugins
+
+TODO
+
+
+```js
+parser.use(function() {});
+```
+
+
+```js
+snapdragon.parser.use(function() {});
+```
diff --git a/support/src/content/plugins.md b/support/src/content/plugins.md
new file mode 100644
index 0000000..ffda807
--- /dev/null
+++ b/support/src/content/plugins.md
@@ -0,0 +1,18 @@
+WIP (draft)
+
+# Snapdragon plugins
+
+```js
+var snapdragon = new Snapdgragon();
+// register plugins
+snapdragon.use(function() {});
+
+// register parser plugins
+snapdragon.parser.use(function() {});
+
+// register compiler plugins
+snapdragon.compiler.use(function() {});
+
+// parse
+var ast = snapdragon.parse('foo/bar');
+```
diff --git a/test/compile.js b/test/compile.js
index 7471f64..7e56753 100644
--- a/test/compile.js
+++ b/test/compile.js
@@ -14,21 +14,17 @@ describe('compiler', function() {
     parser
       .set('text', function() {
         var pos = this.position();
-        var m = this.match(/^\w+/);
-        if (!m) return;
-        return pos({
-          type: 'text',
-          val: m[0]
-        });
+        var match = this.match(/^\w+/);
+        if (match) {
+          return pos(this.node(match[0]));
+        }
       })
       .set('slash', function() {
         var pos = this.position();
-        var m = this.match(/^\//);
-        if (!m) return;
-        return pos({
-          type: 'slash',
-          val: m[0]
-        });
+        var match = this.match(/^\//);
+        if (match) {
+          return pos(this.node(match[0]))
+        }
       });
   });
 
@@ -40,13 +36,13 @@ describe('compiler', function() {
         cb(new Error('expected an error'));
       } catch (err) {
         assert(err);
-        assert.equal(err.message, 'string column:1: compiler "text" is not registered');
+        assert.equal(err.message, 'string <line:1 column:2>: compiler "text" is not registered');
         cb();
       }
     });
   });
 
-  describe('compiling', function() {
+  describe('.compile', function() {
     beforeEach(function() {
       compiler
         .set('text', function(node) {
diff --git a/test/nodes.js b/test/nodes.js
new file mode 100644
index 0000000..193238e
--- /dev/null
+++ b/test/nodes.js
@@ -0,0 +1,70 @@
+'use strict';
+
+require('mocha');
+var assert = require('assert');
+var Snapdragon = require('..');
+var captureSet = require('snapdragon-capture-set');
+var Parser = require('../lib/parser');
+var parser;
+var ast;
+
+describe('parser', function() {
+  beforeEach(function() {
+    parser = new Parser();
+    parser.use(captureSet());
+    parser.captureSet('brace', /^\{/, /^\}/);
+
+    parser.set('text', function() {
+      var pos = this.position();
+      var match = this.match(/^[^{}]/);
+      if (match) {
+        return pos(this.node(match[0]));
+      }
+    });
+
+    parser.set('comma', function() {
+      var pos = this.position();
+      var match = this.match(/,/);
+      if (match) {
+        return pos(this.node(match[0]));
+      }
+    });
+
+    ast = parser.parse('a{b,{c,d},e}f');
+  });
+
+  describe('.isType', function() {
+    it('should return true if "node" is the given "type"', function() {
+      assert(ast.isType('root'));
+      assert(ast.nodes[0].isType('bos'));
+    });
+  });
+
+  describe('.hasType', function() {
+    it('should return true if "node" has the given "type"', function() {
+      assert(ast.hasType('bos'));
+      assert(ast.hasType('eos'));
+    });
+  });
+
+  describe('.first', function() {
+    it('should get the first node in node.nodes', function() {
+      assert(ast.first);
+      assert(ast.first.isType('bos'));
+    });
+  });
+
+  describe('.last', function() {
+    it('should get the last node in node.nodes', function() {
+      assert(ast.last);
+      assert(ast.last.isType('eos'));
+    });
+  });
+
+  describe('.next', function() {
+    it('should get the next node in an array of nodes', function() {
+
+      // console.log(ast)
+    });
+  });
+});
diff --git a/test/parse.js b/test/parse.js
index 3aec24b..25cd738 100644
--- a/test/parse.js
+++ b/test/parse.js
@@ -25,6 +25,42 @@ describe('parser', function() {
     });
   });
 
+  describe('bos', function() {
+    it('should set a beginning-of-string node', function() {
+      var parser = new Parser();
+      parser.set('all', function() {
+        var pos = this.position();
+        var m = this.match(/^.*/);
+        if (!m) return;
+        return pos({
+          type: 'all',
+          val: m[0]
+        });
+      });
+
+      var ast = parser.parse('a/b');
+      assert.equal(ast.nodes[0].type, 'bos');
+    });
+  });
+
+  describe('eos', function() {
+    it('should set an end-of-string node', function() {
+      var parser = new Parser();
+      parser.set('all', function() {
+        var pos = this.position();
+        var m = this.match(/^.*/);
+        if (!m) return;
+        return pos({
+          type: 'all',
+          val: m[0]
+        });
+      });
+
+      var ast = parser.parse('a/b');
+      assert.equal(ast.nodes[ast.nodes.length - 1].type, 'eos');
+    });
+  });
+
   describe('.set():', function() {
     it('should register middleware', function() {
       parser.set('all', function() {
@@ -45,6 +81,7 @@ describe('parser', function() {
       parser.set('all', function() {
         var pos = this.position();
         var m = this.match(/^.*/);
+        if (!m) return;
         return pos({
           type: 'all',
           val: m[0]
@@ -60,6 +97,7 @@ describe('parser', function() {
       parser.set('all', function() {
         var pos = this.position();
         var m = this.match(/^.*/);
+        if (!m) return;
         return pos({
           type: 'all',
           val: m[0]
@@ -135,27 +173,24 @@ describe('ast', function() {
         var m = this.match(/^\w/);
         if (!m) return;
         return pos({
-          type: 'text',
           val: m[0]
         });
       });
 
       parser.set('open', function() {
         var pos = this.position();
-        var m = this.match(/^{/);
+        var m = this.match(/^\{/);
         if (!m) return;
         return pos({
-          type: 'open',
           val: m[0]
         });
       });
 
       parser.set('close', function() {
         var pos = this.position();
-        var m = this.match(/^}/);
+        var m = this.match(/^\}/);
         if (!m) return;
         return pos({
-          type: 'close',
           val: m[0]
         });
       });
@@ -165,7 +200,6 @@ describe('ast', function() {
         var m = this.match(/,/);
         if (!m) return;
         return pos({
-          type: 'comma',
           val: m[0]
         });
       });
diff --git a/test/parser.js b/test/parser.js
index 3bc9010..10bdac0 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -45,6 +45,7 @@ describe('parser', function() {
         parser.set('all', function() {
           var pos = this.position();
           var m = this.match(/^.*/);
+          if (!m) return;
           return pos({
             type: 'all',
             val: m[0]
diff --git a/test/snapdragon.capture.js b/test/snapdragon.capture.js
index dc67936..8904b03 100644
--- a/test/snapdragon.capture.js
+++ b/test/snapdragon.capture.js
@@ -3,11 +3,13 @@
 require('mocha');
 var assert = require('assert');
 var Snapdragon = require('..');
+var capture = require('snapdragon-capture');
 var snapdragon;
 
-describe('parser', function() {
+describe('.capture (plugin usage)', function() {
   beforeEach(function() {
     snapdragon = new Snapdragon();
+    snapdragon.use(capture());
   });
 
   describe('errors', function(cb) {
@@ -26,14 +28,12 @@ describe('parser', function() {
   describe('.capture():', function() {
     it('should register a parser', function() {
       snapdragon.capture('all', /^.*/);
-
       snapdragon.parse('a/b');
       assert(snapdragon.parsers.hasOwnProperty('all'));
     });
 
     it('should use middleware to parse', function() {
       snapdragon.capture('all', /^.*/);
-
       snapdragon.parse('a/b');
       assert.equal(snapdragon.parser.parsed, 'a/b');
       assert.equal(snapdragon.parser.input, '');
@@ -41,7 +41,6 @@ describe('parser', function() {
 
     it('should create ast node:', function() {
       snapdragon.capture('all', /^.*/);
-
       snapdragon.parse('a/b');
       assert.equal(snapdragon.parser.ast.nodes.length, 3);
     });
@@ -60,6 +59,7 @@ describe('parser', function() {
 describe('ast', function() {
   beforeEach(function() {
     snapdragon = new Snapdragon();
+    snapdragon.use(capture());
     snapdragon
         .capture('text', /^\w+/)
         .capture('slash', /^\//);
@@ -71,20 +71,4 @@ describe('ast', function() {
       assert.equal(snapdragon.parser.orig, 'a/b');
     });
   });
-
-  describe('recursion', function() {
-    // TODO!
-    beforeEach(function() {
-      snapdragon
-        .capture('text', /^[^{},]+/)
-        .capture('open', /^\{/)
-        .capture('close', /^\}/)
-        .capture('comma', /,/);
-    });
-
-    it('should set original string on `orig`', function() {
-      snapdragon.parse('a{b,{c,d},e}f');
-      assert.equal(snapdragon.parser.orig, 'a{b,{c,d},e}f');
-    });
-  });
 });
diff --git a/test/compile.js b/test/snapdragon.compile.js
similarity index 52%
copy from test/compile.js
copy to test/snapdragon.compile.js
index 7471f64..09ea728 100644
--- a/test/compile.js
+++ b/test/snapdragon.compile.js
@@ -2,53 +2,47 @@
 
 require('mocha');
 var assert = require('assert');
-var Compile = require('../lib/compiler');
-var Parser = require('../lib/parser');
-var compiler;
+var Snapdragon = require('..');
+var snapdragon;
 var parser;
 
-describe('compiler', function() {
+describe('snapdragon.compiler', function() {
   beforeEach(function() {
-    compiler = new Compile();
-    parser = new Parser();
-    parser
+    snapdragon = new Snapdragon();
+    snapdragon.parser
       .set('text', function() {
         var pos = this.position();
-        var m = this.match(/^\w+/);
-        if (!m) return;
-        return pos({
-          type: 'text',
-          val: m[0]
-        });
+        var match = this.match(/^\w+/);
+        if (match) {
+          return pos(this.node(match[0]));
+        }
       })
       .set('slash', function() {
         var pos = this.position();
-        var m = this.match(/^\//);
-        if (!m) return;
-        return pos({
-          type: 'slash',
-          val: m[0]
-        });
+        var match = this.match(/^\//);
+        if (match) {
+          return pos(this.node(match[0]))
+        }
       });
   });
 
   describe('errors', function(cb) {
     it('should throw an error when a compiler is missing', function(cb) {
       try {
-        var ast = parser.parse('a/b/c');
-        compiler.compile(ast);
+        var ast = snapdragon.parse('a/b/c');
+        snapdragon.compile(ast);
         cb(new Error('expected an error'));
       } catch (err) {
         assert(err);
-        assert.equal(err.message, 'string column:1: compiler "text" is not registered');
+        assert.equal(err.message, 'string <line:1 column:2>: compiler "text" is not registered');
         cb();
       }
     });
   });
 
-  describe('compiling', function() {
+  describe('snapdragon.compile', function() {
     beforeEach(function() {
-      compiler
+      snapdragon.compiler
         .set('text', function(node) {
           return this.emit(node.val);
         })
@@ -58,8 +52,8 @@ describe('compiler', function() {
     });
 
     it('should set the result on `output`', function() {
-      var ast = parser.parse('a/b/c');
-      var res = compiler.compile(ast);
+      var ast = snapdragon.parse('a/b/c');
+      var res = snapdragon.compile(ast);
       assert.equal(res.output, 'a-b-c');
     });
   });
diff --git a/test/snapdragon.regex.js b/test/snapdragon.regex.js
index 85bca4d..705feb9 100644
--- a/test/snapdragon.regex.js
+++ b/test/snapdragon.regex.js
@@ -3,11 +3,13 @@
 require('mocha');
 var assert = require('assert');
 var Snapdragon = require('..');
+var capture = require('snapdragon-capture');
 var snapdragon;
 
 describe('parser', function() {
   beforeEach(function() {
     snapdragon = new Snapdragon();
+    snapdragon.use(capture());
   });
 
   describe('.regex():', function() {
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index 22fb9eb..0000000
--- a/test/test.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-
-require('mocha');
-var assert = require('assert');
-var extglob = require('..');
-
-describe('extglob', function() {
-  it('should export a function', function() {
-    assert.equal(typeof extglob, 'function');
-  });
-});
diff --git a/test/utils.js b/test/utils.js
deleted file mode 100644
index 16394fc..0000000
--- a/test/utils.js
+++ /dev/null
@@ -1,13 +0,0 @@
-'use strict';
-
-require('mocha');
-var assert = require('assert');
-var utils = require('../lib/utils');
-
-describe('utils', function() {
-  describe('main export:', function() {
-    it('should expose an object', function() {
-      assert.equal(typeof utils, 'object');
-    });
-  });
-});
diff --git a/verbfile.js b/verbfile.js
new file mode 100644
index 0000000..7259504
--- /dev/null
+++ b/verbfile.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = function(verb) {
+  verb.use(require('verb-generate-readme'));
+
+  verb.task('docs', function(cb) {
+    return verb.src('support/src/content/*.md', {cwd: __dirname})
+      .pipe(verb.renderFile('md', {layout: null}))
+      .pipe(verb.dest('docs'))
+  });
+
+  verb.task('default', ['docs', 'readme']);
+};

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-snapdragon.git



More information about the Pkg-javascript-commits mailing list