pax_global_header 0000666 0000000 0000000 00000000064 14034043604 0014510 g ustar 00root root 0000000 0000000 52 comment=fb340c536871669c83601ce6f6cb49d9ff730fac
snapdragon-0.12.1/ 0000775 0000000 0000000 00000000000 14034043604 0013725 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/.editorconfig 0000664 0000000 0000000 00000000441 14034043604 0016401 0 ustar 00root root 0000000 0000000 # http://editorconfig.org/
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{**/{actual,fixtures,expected,templates}/**,*.md}]
trim_trailing_whitespace = false
insert_final_newline = false
snapdragon-0.12.1/.eslintrc.json 0000664 0000000 0000000 00000007325 14034043604 0016530 0 ustar 00root root 0000000 0000000 {
"extends": [
"eslint:recommended"
],
"env": {
"browser": false,
"es6": true,
"node": true,
"mocha": true
},
"parserOptions":{
"ecmaVersion": 9,
"sourceType": "module",
"ecmaFeatures": {
"modules": true,
"experimentalObjectRestSpread": true
}
},
"globals": {
"document": false,
"navigator": false,
"window": false
},
"rules": {
"accessor-pairs": 2,
"arrow-spacing": [2, { "before": true, "after": true }],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-dangle": [2, "never"],
"comma-spacing": [2, { "before": false, "after": true }],
"comma-style": [2, "last"],
"constructor-super": 2,
"curly": [2, "multi-line"],
"dot-location": [2, "property"],
"eol-last": 2,
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [2, "^(err|error)$" ],
"indent": [2, 2, { "SwitchCase": 1 }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, { "before": true, "after": true }],
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
"new-parens": 2,
"no-array-constructor": 2,
"no-caller": 2,
"no-class-assign": 2,
"no-cond-assign": 2,
"no-const-assign": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-dupe-args": 2,
"no-dupe-class-members": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-empty-character-class": 2,
"no-eval": 2,
"no-ex-assign": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-parens": [2, "functions"],
"no-fallthrough": 2,
"no-floating-decimal": 2,
"no-func-assign": 2,
"no-implied-eval": 2,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-multiple-empty-lines": [2, { "max": 1 }],
"no-native-reassign": 0,
"no-negated-in-lhs": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-object": 2,
"no-new-require": 2,
"no-new-wrappers": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-octal-escape": 2,
"no-proto": 0,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-return-assign": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow-restricted-names": 2,
"no-spaced-func": 2,
"no-sparse-arrays": 2,
"no-this-before-super": 2,
"no-throw-literal": 2,
"no-trailing-spaces": 0,
"no-undef": 2,
"no-undef-init": 2,
"no-unexpected-multiline": 2,
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
"no-unreachable": 2,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-useless-call": 0,
"no-with": 2,
"one-var": [0, { "initialized": "never" }],
"operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
"padded-blocks": [0, "never"],
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
"semi": [2, "always"],
"semi-spacing": [2, { "before": false, "after": true }],
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-in-parens": [2, "never"],
"space-infix-ops": 2,
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
"use-isnan": 2,
"valid-typeof": 2,
"wrap-iife": [2, "any"],
"yoda": [2, "never"]
}
}
snapdragon-0.12.1/.gitattributes 0000664 0000000 0000000 00000000017 14034043604 0016616 0 ustar 00root root 0000000 0000000 *.* text eol=lf snapdragon-0.12.1/.gitignore 0000664 0000000 0000000 00000000456 14034043604 0015722 0 ustar 00root root 0000000 0000000 # always ignore files
*.DS_Store
.idea
.vscode
*.sublime-*
# test related, or directories generated by tests
test/actual
actual
coverage
.nyc*
# npm
node_modules
npm-debug.log
# yarn
yarn.lock
yarn-error.log
# misc
_gh_pages
_draft
_drafts
bower_components
vendor
temp
tmp
TODO.md
package-lock.json snapdragon-0.12.1/.travis.yml 0000664 0000000 0000000 00000000247 14034043604 0016041 0 ustar 00root root 0000000 0000000 sudo: false
os:
- linux
- osx
language: node_js
node_js:
- node
- '14'
- '12'
- '10'
- '9'
- '8'
- '7'
- '6'
- '5'
- '4'
- '0.12'
- '0.10'
snapdragon-0.12.1/.verb.md 0000664 0000000 0000000 00000012322 14034043604 0015263 0 ustar 00root root 0000000 0000000 Created by [jonschlinkert]({%= author.url %}) and [doowb](https://github.com/doowb).
**Features**
- 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][] and [css][].
## Quickstart example
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 a string**
```js
var ast = snapdragon.parser
// parser handlers (essentially middleware)
// used for parsing substrings to create tokens
.set('foo', function () {})
.set('bar', function () {})
.parse('some string', options);
```
**Compile an AST returned from `.parse()`**
```js
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 () {})
// pass the `ast` from the parse method
.compile(ast)
// the compiled string
console.log(result.output);
```
See the [examples](./examples/).
## Parsing
**Parser handlers**
Parser handlers are middleware functions responsible for matching substrings to create tokens:
**Example handler**
```js
var ast = snapdragon.parser
.set('dot', function() {
var pos = this.position();
var m = this.match(/^\./);
if (!m) return;
return pos({
// 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: '.'
}
```
**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',
val: '.',
position:
{ start: { lineno: 1, column: 1 },
end: { lineno: 1, column: 2 } }}
```
**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
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 result = snapdragon.compiler
.set('dot', function (node) {
console.log(node.val)
//=> '.'
return this.emit(node.val);
})
```
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 entire node as the second argument as well (this allows the compiler to get the `node.position`).
```js
var res = snapdragon.compiler
.set('dot', function (node) {
return this.emit(node.val, node);
})
```
## All together
This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
```js
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({
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);
//=> '\.'
```
## API
### Parse
{%= apidocs("lib/parser.js") %}
### 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`
snapdragon-0.12.1/LICENSE 0000664 0000000 0000000 00000002100 14034043604 0014723 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2015-2018, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
snapdragon-0.12.1/README.md 0000664 0000000 0000000 00000046260 14034043604 0015214 0 ustar 00root root 0000000 0000000 # snapdragon [](https://www.npmjs.com/package/snapdragon) [](https://npmjs.org/package/snapdragon) [](https://npmjs.org/package/snapdragon) [](https://travis-ci.org/here-be/snapdragon)
> Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Table of Contents
Details
- [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)
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save snapdragon
```
Created by [jonschlinkert](https://github.com/jonschlinkert) and [doowb](https://github.com/doowb).
**Features**
* 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](https://pugjs.org/) and [css](https://github.com/reworkcss/css).
## Quickstart example
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 a string**
```js
var ast = snapdragon.parser
// parser handlers (essentially middleware)
// used for parsing substrings to create tokens
.set('foo', function () {})
.set('bar', function () {})
.parse('some string', options);
```
**Compile an AST returned from `.parse()`**
```js
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 () {})
// pass the `ast` from the parse method
.compile(ast)
// the compiled string
console.log(result.output);
```
See the [examples](./examples/).
## Parsing
**Parser handlers**
Parser handlers are middleware functions responsible for matching substrings to create tokens:
**Example handler**
```js
var ast = snapdragon.parser
.set('dot', function() {
var pos = this.position();
var m = this.match(/^\./);
if (!m) return;
return pos({
// 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: '.'
}
```
**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',
val: '.',
position:
{ start: { lineno: 1, column: 1 },
end: { lineno: 1, column: 2 } }}
```
**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
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 result = snapdragon.compiler
.set('dot', function (node) {
console.log(node.val)
//=> '.'
return this.emit(node.val);
})
```
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 entire node as the second argument as well (this allows the compiler to get the `node.position`).
```js
var res = snapdragon.compiler
.set('dot', function (node) {
return this.emit(node.val, node);
})
```
## All together
This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
```js
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({
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);
//=> '\.'
```
## API
### [Parser](lib/parser.js#L27)
Create a new `Parser` with the given `input` and `options`.
**Params**
* `input` **{String}**
* `options` **{Object}**
**Example**
```js
var Snapdragon = require('snapdragon');
var Parser = Snapdragon.Parser;
var parser = new Parser();
```
### [.error](lib/parser.js#L97)
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.set('foo', function(node) {
if (node.val !== 'foo') {
throw this.error('expected node.val to be "foo"', node);
}
});
```
### [.define](lib/parser.js#L115)
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.
**Example**
```js
parser.define('foo', 'bar');
```
### [.node](lib/parser.js#L133)
Create a new [Node](#node) with the given `val` and `type`.
**Params**
* `val` **{Object}**
* `type` **{String}**
* `returns` **{Object}**: returns the [Node](#node) instance.
**Example**
```js
parser.node('/', 'slash');
```
### [.position](lib/parser.js#L155)
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#L187)
Add parser `type` with the given visitor `fn`.
**Params**
* `type` **{String}**
* `fn` **{Function}**
**Example**
```js
parser.set('all', function() {
var match = this.match(/^./);
if (match) {
return this.node(match[0]);
}
});
```
### [.get](lib/parser.js#L206)
Get parser `type`.
**Params**
* `type` **{String}**
**Example**
```js
var fn = parser.get('slash');
```
### [.push](lib/parser.js#L229)
Push a node onto the stack for the given `type`.
**Params**
* `type` **{String}**
* `returns` **{Object}** `token`
**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#L261)
Pop a token off of the stack of the given `type`.
**Params**
* `type` **{String}**
* `returns` **{Object}**: Returns a token
**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#L294)
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**
* `type` **{String}**
* `returns` **{Boolean}**
**Example**
```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');
}
});
```
### [.isType](lib/parser.js#L324)
Return true if `node` is the given `type`.
**Params**
* `node` **{Object}**
* `type` **{String}**
* `returns` **{Boolean}**
**Example**
```js
parser.isType(node, 'brace');
```
### [.prev](lib/parser.js#L340)
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#L394)
Match `regex`, return captures, and update the cursor position by `match[0]` length.
**Params**
* `regex` **{RegExp}**
* `returns` **{Object}**
**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
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.
**Example**
```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**
* `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): Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support… [more](https://github.com/micromatch/braces) | [homepage](https://github.com/micromatch/braces "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with 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): Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob… [more](https://github.com/micromatch/extglob) | [homepage](https://github.com/micromatch/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/micromatch/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/micromatch/nanomatch) | [homepage](https://github.com/micromatch/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
Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
Running Tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
Building docs
_(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, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Related projects
A few of the libraries that use snapdragon:
* [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/here-be/snapdragon-util "Utilities for the snapdragon parser/compiler.")
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 156 | [jonschlinkert](https://github.com/jonschlinkert) |
| 3 | [doowb](https://github.com/doowb) |
| 2 | [danez](https://github.com/danez) |
| 1 | [EdwardBetts](https://github.com/EdwardBetts) |
### Author
**Jon Schlinkert**
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
### License
Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on March 20, 2018._ snapdragon-0.12.1/docs/ 0000775 0000000 0000000 00000000000 14034043604 0014655 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/docs/compiling.md 0000664 0000000 0000000 00000000767 14034043604 0017172 0 ustar 00root root 0000000 0000000 # Compiling with snapdragon
Pre-requisites
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!
snapdragon-0.12.1/docs/core-concepts.md 0000664 0000000 0000000 00000001330 14034043604 0017740 0 ustar 00root root 0000000 0000000 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][]
snapdragon-0.12.1/docs/crash-course.md 0000664 0000000 0000000 00000006045 14034043604 0017602 0 ustar 00root root 0000000 0000000 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
snapdragon-0.12.1/docs/getting-started.md 0000664 0000000 0000000 00000001437 14034043604 0020311 0 ustar 00root root 0000000 0000000 WIP (draft)
# Getting started
[What is snapdragon, and who created it?](overview.html)
- 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,
snapdragon-0.12.1/docs/options.md 0000664 0000000 0000000 00000000027 14034043604 0016671 0 ustar 00root root 0000000 0000000 # Options
WIP (draft)
snapdragon-0.12.1/docs/overview.md 0000664 0000000 0000000 00000003233 14034043604 0017046 0 ustar 00root root 0000000 0000000 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]() for notifications and updates about my github projects
- follow me on [twitter]()
- connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert)
snapdragon-0.12.1/docs/parsing.md 0000664 0000000 0000000 00000004125 14034043604 0016644 0 ustar 00root root 0000000 0000000 WIP (draft)
# Parsing with snapdragon
Pre-requisites
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!
Table of contents
- Usage
- Developer
* Parser
* Parsers
* Custom parsers
## 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() {});
```
snapdragon-0.12.1/docs/plugins.md 0000664 0000000 0000000 00000000475 14034043604 0016666 0 ustar 00root root 0000000 0000000 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');
```
snapdragon-0.12.1/examples/ 0000775 0000000 0000000 00000000000 14034043604 0015543 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/examples/dot.js 0000664 0000000 0000000 00000001311 14034043604 0016663 0 ustar 00root root 0000000 0000000 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);
//=> '\.'
snapdragon-0.12.1/examples/errors.js 0000664 0000000 0000000 00000001160 14034043604 0017413 0 ustar 00root root 0000000 0000000 '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@github.com:foo/bar.git');
console.log(ast);
snapdragon-0.12.1/examples/guide-examples.js 0000664 0000000 0000000 00000000212 14034043604 0021005 0 ustar 00root root 0000000 0000000 var Snapdragon = require('..');
var snapdragon = new Snapdragon();
/**
*
*/
var ast = snapdragon.parse('foo/*.js');
console.log(ast);
snapdragon-0.12.1/examples/parser.js 0000664 0000000 0000000 00000001651 14034043604 0017400 0 ustar 00root root 0000000 0000000 '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@github.com:foo/bar.git');
console.log(ast);
snapdragon-0.12.1/examples/tiny-globs.js 0000664 0000000 0000000 00000001513 14034043604 0020170 0 ustar 00root root 0000000 0000000 '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'));
snapdragon-0.12.1/gulpfile.js 0000664 0000000 0000000 00000001510 14034043604 0016067 0 ustar 00root root 0000000 0000000 'use strict';
var gulp = require('gulp');
var istanbul = require('gulp-istanbul');
var eslint = require('gulp-eslint');
var mocha = require('gulp-mocha');
var unused = require('gulp-unused');
gulp.task('coverage', function() {
return gulp.src(['lib/*.js', 'index.js'])
.pipe(istanbul({includeUntested: true}))
.pipe(istanbul.hookRequire());
});
gulp.task('mocha', ['coverage'], function() {
return gulp.src(['test/*.js'])
.pipe(mocha())
.pipe(istanbul.writeReports());
});
gulp.task('eslint', function() {
return gulp.src(['*.js', 'lib/*.js', 'test/*.js'])
.pipe(eslint())
.pipe(eslint.format());
});
gulp.task('unused', function() {
return gulp.src(['index.js', 'lib/*.js'])
.pipe(unused({keys: Object.keys(require('./lib/utils.js'))}));
});
gulp.task('default', ['coverage', 'eslint', 'mocha']);
snapdragon-0.12.1/index.js 0000664 0000000 0000000 00000012312 14034043604 0015371 0 ustar 00root root 0000000 0000000 'use strict';
var define = require('define-property');
var extend = require('extend-shallow');
var Compiler = require('./lib/compiler');
var Parser = require('./lib/parser');
/**
* 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) {
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'}, options);
this.isSnapdragon = true;
this.plugins = {
fns: [],
preprocess: [],
visitors: {},
before: {},
after: {}
};
}
/**
* Register a plugin `fn`.
*
* ```js
* var snapdragon = new Snapdgragon([options]);
* snapdragon.use(function() {
* console.log(this); //<= snapdragon instance
* console.log(this.parser); //<= parser instance
* console.log(this.compiler); //<= compiler instance
* });
* ```
* @param {Object} `fn`
* @api public
*/
Snapdragon.prototype.use = function(fn) {
fn.call(this, this);
return this;
};
/**
* 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
* 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.
*
* ```js
* var snapdragon = new Snapdgragon([options]);
* var ast = snapdragon.parse('foo/bar');
* console.log(ast);
* ```
* @param {String} `str`
* @param {Object} `options` Set `options.sourcemap` to true to enable source maps.
* @return {Object} Returns an AST.
* @api public
*/
Snapdragon.prototype.parse = function(str, options) {
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 an `ast` returned from `snapdragon.parse()`
*
* ```js
* // compile
* var res = snapdragon.compile(ast);
* // get the compiled output string
* console.log(res.output);
* ```
* @param {Object} `ast`
* @param {Object} `options`
* @return {Object} Returns an object with an `output` property with the rendered string.
* @api public
*/
Snapdragon.prototype.compile = function(ast, 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
*/
Snapdragon.prototype.render = function(ast, options) {
if (typeof ast === 'string') {
ast = this.parse(ast, options);
}
return this.compile(ast, options).output;
};
/**
* Get or set a `Snapdragon.Compiler` instance.
* @api public
*/
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`
*/
Snapdragon.Compiler = Compiler;
Snapdragon.Parser = Parser;
/**
* Expose `Snapdragon`
*/
module.exports = Snapdragon;
snapdragon-0.12.1/lib/ 0000775 0000000 0000000 00000000000 14034043604 0014473 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/lib/compiler.js 0000664 0000000 0000000 00000014006 14034043604 0016644 0 ustar 00root root 0000000 0000000 '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 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) {
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('bos', function(node) {
return this.emit(node.val, node);
});
use(this);
}
/**
* Prototype methods
*/
Compiler.prototype = {
/**
* 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*/) {
return error.apply(this, arguments);
},
/**
* Concat the given string to `compiler.output`.
*
* ```js
* compiler.set('foo', function(node) {
* this.emit(node.val, node);
* });
* ```
* @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
*/
emit: function(val, node) {
this.output += val;
return 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
*/
noop: function(node) {
this.emit('', node);
},
/**
* 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
*/
define: function(key, val) {
define(this, key, val);
return this;
},
/**
* 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
*/
set: function(type, fn) {
this.compilers[type] = fn;
return this;
},
/**
* Get the compiler of the given `type`.
*
* ```js
* var fn = compiler.get('slash');
* ```
* @name .get
* @param {String} `type`
* @api public
*/
get: function(type) {
return this.compilers[type];
},
/**
* 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
*/
visit: function(node) {
if (util.isOpen(node)) {
util.addType(this.state, node);
}
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);
}
var val = fn.call(this, node) || node;
if (util.isNode(val)) {
node = val;
}
if (util.isClose(node)) {
util.removeType(this.state, node);
}
return node;
},
/**
* 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(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;
}
},
/**
* 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 = extend({}, this.options, options);
this.ast = ast;
this.output = '';
// source map support
if (opts.sourcemap) {
var sourcemaps = require('./source-maps');
sourcemaps(this);
this.mapVisit(this.ast);
this.applySourceMaps();
this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON();
} else {
this.mapVisit(this.ast);
}
return this;
}
};
/**
* Expose `Compiler`
*/
module.exports = Compiler;
snapdragon-0.12.1/lib/error.js 0000664 0000000 0000000 00000001037 14034043604 0016163 0 ustar 00root root 0000000 0000000 '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 + ' : ' + 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;
}
};
snapdragon-0.12.1/lib/parser.js 0000664 0000000 0000000 00000031337 14034043604 0016334 0 ustar 00root root 0000000 0000000 'use strict';
var use = require('use');
var util = require('snapdragon-util');
var Cache = require('map-cache');
var Node = require('snapdragon-node');
var define = require('define-property');
var extend = require('extend-shallow');
var Emitter = require('component-emitter');
var isObject = require('isobject');
var Position = require('./position');
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) {
this.options = extend({source: 'string'}, options);
this.isParser = true;
this.Node = Node;
this.init(this.options);
use(this);
}
/**
* Prototype methods
*/
Parser.prototype = Emitter({
constructor: Parser,
init: function(options) {
this.orig = '';
this.input = '';
this.parsed = '';
this.currentType = 'root';
this.setCount = 0;
this.count = 0;
this.column = 1;
this.line = 1;
this.regex = new Cache();
this.errors = this.errors || [];
this.parsers = this.parsers || {};
this.types = this.types || [];
this.sets = this.sets || {};
this.fns = this.fns || [];
this.tokens = [];
this.stack = [];
this.typeStack = [];
this.setStack = [];
var pos = this.position();
this.bos = pos(this.node({
type: 'bos',
val: ''
}));
this.ast = pos(this.node({
type: this.options.astType || 'root',
errors: this.errors
}));
this.ast.pushNode(this.bos);
this.nodes = [this.ast];
},
/**
* 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*/) {
return error.apply(this, arguments);
},
/**
* Define a non-enumberable property on the `Parser` instance. This is useful
* in plugins, for exposing methods inside handlers.
*
* ```js
* parser.define('foo', 'bar');
* ```
* @name .define
* @param {String} `key` propery name
* @param {any} `val` property value
* @return {Object} Returns the Parser instance for chaining.
* @api public
*/
define: function(key, val) {
define(this, key, val);
return this;
},
/**
* 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) {
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;
};
},
/**
* 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
*/
set: function(type, fn) {
if (this.types.indexOf(type) === -1) {
this.types.push(type);
}
this.parsers[type] = fn.bind(this);
return this;
},
/**
* Get parser `type`.
*
* ```js
* var fn = parser.get('slash');
* ```
* @name .get
* @param {String} `type`
* @api public
*/
get: function(type) {
return this.parsers[type];
},
/**
* 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
*/
push: function(type, token) {
this.sets[type] = this.sets[type] || [];
this.count++;
this.stack.push(token);
this.setStack.push(token);
this.typeStack.push(type);
return this.sets[type].push(token);
},
/**
* 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
*/
pop: function(type) {
if (this.sets[type]) {
this.count--;
this.stack.pop();
this.setStack.pop();
this.typeStack.pop();
return this.sets[type].pop();
}
},
/**
* 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) {
if (typeof type === 'undefined') {
return this.count > 0;
}
if (!Array.isArray(this.sets[type])) {
return false;
}
return this.sets[type].length > 0;
},
isDirectlyInside: function(type) {
if (typeof type === 'undefined') {
return this.count > 0 ? util.last(this.typeStack) : null;
}
return util.last(this.typeStack) === type;
},
/**
* Return true if `node` is the given `type`.
*
* ```js
* parser.isType(node, 'brace');
* ```
* @name .isType
* @param {Object} `node`
* @param {String} `type`
* @return {Boolean}
* @api public
*/
isType: function(node, type) {
return node && node.type === type;
},
/**
* 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
? util.last(this.stack, n)
: util.last(this.nodes, n);
},
/**
* Update line and column based on `str`.
*/
consume: function(len) {
this.input = this.input.substr(len);
},
/**
* Returns the string up to the given `substring`,
* if it exists, and advances the cursor position past the substring.
*/
advanceTo: function(str, i) {
var idx = this.input.indexOf(str, i);
if (idx !== -1) {
var val = this.input.slice(0, idx);
this.consume(idx + str.length);
return val;
}
},
/**
* Update column based on `str`.
*/
updatePosition: function(str, len) {
var lines = str.match(/\n/g);
if (lines) this.line += lines.length;
var i = str.lastIndexOf('\n');
this.column = ~i ? len - i : this.column + len;
this.parsed += str;
this.consume(len);
},
/**
* 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) {
var m = regex.exec(this.input);
if (m) {
this.updatePosition(m[0], m[0].length);
return m;
}
},
/**
* Push `node` to `parent.nodes` and assign `node.parent`
*/
pushNode: function(node, parent) {
if (node && parent) {
if (parent === node) parent = this.ast;
define(node, 'parent', parent);
if (parent.nodes) parent.nodes.push(node);
if (this.sets.hasOwnProperty(parent.type)) {
this.currentType = parent.type;
}
}
},
/**
* Capture end-of-string
*/
eos: function() {
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:' + prev);
}
if (!util.hasOpenAndClose(prev)) {
define(prev.parent, 'escaped', true);
define(prev, '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 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(node, 'parent', this.ast);
return node;
},
/**
* Run parsers to advance the cursor position
*/
getNext: function() {
var parsed = this.parsed;
var len = this.types.length;
var idx = -1;
while (++idx < len) {
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;
}
}
},
/**
* 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) {
if (typeof input !== 'string') {
throw new TypeError('expected a string');
}
this.init(this.options);
this.orig = input;
this.input = input;
// run parsers
while (this.input) this.advance();
// balance unmatched sets, if not disabled
balanceSets(this, this.stack.pop());
// create end-of-string node
var eos = this.eos();
var ast = this.prev();
if (ast.type === 'root') {
this.pushNode(eos, ast);
}
return this.ast;
},
/**
* Visit `node` with the given `fn`
*/
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`.
*/
mapVisit: function(nodes, fn, parent) {
for (var i = 0; i < nodes.length; i++) {
this.visit(nodes[i], fn);
}
}
});
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;
}
}
/**
* Expose `Parser`
*/
module.exports = Parser;
snapdragon-0.12.1/lib/position.js 0000664 0000000 0000000 00000000301 14034043604 0016667 0 ustar 00root root 0000000 0000000 'use strict';
/**
* Store the position for a node
*/
module.exports = function Position(start, parser) {
this.start = start;
this.end = { line: parser.line, column: parser.column };
};
snapdragon-0.12.1/lib/source-maps.js 0000664 0000000 0000000 00000007262 14034043604 0017276 0 ustar 00root root 0000000 0000000 'use strict';
var fs = require('fs');
var path = require('path');
var define = require('define-property');
var sourceMapResolve = require('source-map-resolve');
var SourceMap = require('source-map');
/**
* Expose `mixin()`.
* This code is based on `source-maps-support.js` in reworkcss/css
* https://github.com/reworkcss/css/blob/master/lib/stringify/source-map-support.js
* Copyright (c) 2012 TJ Holowaychuk
*/
module.exports = mixin;
/**
* Mixin source map support into `compiler`.
*
* @param {Object} `compiler`
* @api public
*/
function mixin(compiler) {
define(compiler, '_comment', compiler.comment);
compiler.map = new SourceMap.SourceMapGenerator();
compiler.position = { line: 1, column: 1 };
compiler.content = {};
compiler.files = {};
for (var key in exports) {
define(compiler, key, exports[key]);
}
}
/**
* Update position.
*
* @param {String} str
*/
exports.updatePosition = function(str) {
var lines = str.match(/\n/g);
if (lines) this.position.line += lines.length;
var i = str.lastIndexOf('\n');
this.position.column = ~i ? str.length - i : this.position.column + str.length;
};
/**
* Emit `str` with `position`.
*
* @param {String} str
* @param {Object} [pos]
* @return {String}
*/
exports.emit = function(str, node) {
var position = node.position || {};
var source = position.source;
if (source) {
if (position.filepath) {
source = unixify(position.filepath);
}
this.map.addMapping({
source: source,
generated: {
line: this.position.line,
column: Math.max(this.position.column - 1, 0)
},
original: {
line: position.start.line,
column: position.start.column - 1
}
});
if (position.content) {
this.addContent(source, position);
}
if (position.filepath) {
this.addFile(source, position);
}
}
this.updatePosition(str);
this.output += str;
return str;
};
/**
* Adds a file to the source map output if it has not already been added
* @param {String} `file`
* @param {Object} `pos`
*/
exports.addFile = function(file, position) {
if (typeof position.content !== 'string') return;
if (Object.prototype.hasOwnProperty.call(this.files, file)) return;
this.files[file] = position.content;
};
/**
* Adds a content source to the source map output if it has not already been added
* @param {String} `source`
* @param {Object} `position`
*/
exports.addContent = function(source, position) {
if (typeof position.content !== 'string') return;
if (Object.prototype.hasOwnProperty.call(this.content, source)) return;
this.map.setSourceContent(source, position.content);
};
/**
* Applies any original source maps to the output and embeds the source file
* contents in the source map.
*/
exports.applySourceMaps = function() {
Object.keys(this.files).forEach(function(file) {
var content = this.files[file];
this.map.setSourceContent(file, content);
if (this.options.inputSourcemaps === true) {
var originalMap = sourceMapResolve.resolveSync(content, file, fs.readFileSync);
if (originalMap) {
var map = new SourceMap.SourceMapConsumer(originalMap.map);
var relativeTo = originalMap.sourcesRelativeTo;
this.map.applySourceMap(map, file, unixify(path.dirname(relativeTo)));
}
}
}, this);
};
/**
* Process comments, drops sourceMap comments.
* @param {Object} node
*/
exports.comment = function(node) {
if (/^# sourceMappingURL=/.test(node.comment)) {
return this.emit('', node.position);
}
return this._comment(node);
};
/**
* Convert backslash in the given string to forward slashes
*/
function unixify(fp) {
return fp.split(/\\+/).join('/');
}
snapdragon-0.12.1/package.json 0000664 0000000 0000000 00000004310 14034043604 0016211 0 ustar 00root root 0000000 0000000 {
"name": "snapdragon",
"description": "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.",
"version": "0.12.1",
"homepage": "https://github.com/here-be/snapdragon",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Brian Woodward (https://twitter.com/doowb)",
"Daniel Tschinder (https://github.com/danez)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)"
],
"repository": "here-be/snapdragon",
"bugs": {
"url": "https://github.com/here-be/snapdragon/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"component-emitter": "^1.2.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"get-value": "^2.0.6",
"isobject": "^3.0.0",
"map-cache": "^0.2.2",
"snapdragon-node": "^1.0.6",
"snapdragon-util": "^4.0.0",
"source-map": "^0.5.6",
"source-map-resolve": "^0.6.0",
"use": "^3.1.0"
},
"devDependencies": {
"mocha": "^3.2.0",
"snapdragon-capture-set": "^1.0.1",
"snapdragon-capture": "^0.2.0",
"gulp": "^3.9.1",
"gulp-istanbul": "^1.1.1",
"gulp-eslint": "^3.0.1",
"gulp-mocha": "^3.0.1",
"gulp-unused": "^0.2.1",
"gulp-format-md": "^0.1.11",
"verb-generate-readme": "^0.6.0"
},
"keywords": [
"lexer",
"snapdragon"
],
"verb": {
"toc": "collapsible",
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"related": {
"description": "A few of the libraries that use snapdragon:",
"implementations": [
"braces",
"breakdance",
"expand-brackets",
"extglob",
"micromatch",
"nanomatch"
],
"list": [
"snapdragon-capture",
"snapdragon-capture-set",
"snapdragon-node",
"snapdragon-util"
]
},
"reflinks": [
"css",
"pug",
"snapdragon-capture",
"snapdragon-capture-set",
"snapdragon-node"
],
"lint": {
"reflinks": true
}
}
}
snapdragon-0.12.1/support/ 0000775 0000000 0000000 00000000000 14034043604 0015441 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/support/src/ 0000775 0000000 0000000 00000000000 14034043604 0016230 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/support/src/content/ 0000775 0000000 0000000 00000000000 14034043604 0017702 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/support/src/content/compiling.md 0000664 0000000 0000000 00000000767 14034043604 0022217 0 ustar 00root root 0000000 0000000 # Compiling with snapdragon
Pre-requisites
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!
snapdragon-0.12.1/support/src/content/core-concepts.md 0000664 0000000 0000000 00000001332 14034043604 0022767 0 ustar 00root root 0000000 0000000 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][]
snapdragon-0.12.1/support/src/content/crash-course.md 0000664 0000000 0000000 00000006050 14034043604 0022623 0 ustar 00root root 0000000 0000000 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
snapdragon-0.12.1/support/src/content/getting-started.md 0000664 0000000 0000000 00000001466 14034043604 0023340 0 ustar 00root root 0000000 0000000 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,
snapdragon-0.12.1/support/src/content/guides/ 0000775 0000000 0000000 00000000000 14034043604 0021162 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/support/src/content/guides/creating-your-first-parser.md 0000664 0000000 0000000 00000010657 14034043604 0026724 0 ustar 00root root 0000000 0000000 ---
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();
/**
*
*/
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 : 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 surprise 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.
###
snapdragon-0.12.1/support/src/content/options.md 0000664 0000000 0000000 00000000027 14034043604 0021716 0 ustar 00root root 0000000 0000000 # Options
WIP (draft)
snapdragon-0.12.1/support/src/content/overview.md 0000664 0000000 0000000 00000003270 14034043604 0022074 0 ustar 00root root 0000000 0000000 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)
snapdragon-0.12.1/support/src/content/parsing.md 0000664 0000000 0000000 00000004133 14034043604 0021670 0 ustar 00root root 0000000 0000000 WIP (draft)
# Parsing with snapdragon
Pre-requisites
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!
Table of contents
- Usage
- Developer
* Parser
* Parsers
* Custom parsers
## 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() {});
```
snapdragon-0.12.1/support/src/content/plugins.md 0000664 0000000 0000000 00000000475 14034043604 0021713 0 ustar 00root root 0000000 0000000 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');
```
snapdragon-0.12.1/test/ 0000775 0000000 0000000 00000000000 14034043604 0014704 5 ustar 00root root 0000000 0000000 snapdragon-0.12.1/test/compile.js 0000664 0000000 0000000 00000004420 14034043604 0016672 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Compile = require('../lib/compiler');
var Parser = require('../lib/parser');
var compiler;
var parser;
describe('compiler', function() {
beforeEach(function() {
compiler = new Compile();
compiler
.set('parens.open', function(node) {
return this.emit('(', node);
})
.set('parens.close', function(node) {
return this.emit(')', node);
});
parser = new Parser();
parser
.set('text', function() {
var pos = this.position();
var match = this.match(/^\w+/);
if (match) {
return pos(this.node(match[0]));
}
})
.set('slash', function() {
var pos = this.position();
var match = this.match(/^\//);
if (match) {
return pos(this.node(match[0]))
}
})
.set('parens.open', function() {
var pos = this.position();
var match = this.match(/^\(/);
if (match) {
return pos(this.node(match[0]))
}
})
.set('parens.close', function() {
var pos = this.position();
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);
cb(new Error('expected an error'));
} catch (err) {
assert(err);
assert.equal(err.message, 'string : compiler "text" is not registered');
cb();
}
});
});
describe('.compile', function() {
beforeEach(function() {
compiler
.set('text', function(node) {
return this.emit(node.val);
})
.set('slash', function(node) {
return this.emit('-');
});
});
it('should set the result on `output`', function() {
var ast = parser.parse('a/b/c');
var res = compiler.compile(ast);
assert.equal(res.output, 'a-b-c');
});
it('should compile close without open', function() {
var ast = parser.parse('a)');
var res = compiler.compile(ast);
assert.equal(res.output, 'a)');
});
});
});
snapdragon-0.12.1/test/compiler.js 0000664 0000000 0000000 00000001440 14034043604 0017053 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Compiler = require('../lib/compiler');
var compiler;
describe('compiler', function() {
beforeEach(function() {
compiler = new Compiler();
});
describe('constructor:', function() {
it('should return an instance of Compiler:', function() {
assert(compiler instanceof Compiler);
});
});
// ensures that we catch and document API changes
describe('prototype methods:', function() {
var methods = [
'error',
'set',
'emit',
'visit',
'mapVisit',
'compile'
];
methods.forEach(function(method) {
it('should expose the `' + method + '` method', function() {
assert.equal(typeof compiler[method], 'function', method);
});
});
});
});
snapdragon-0.12.1/test/nodes.js 0000664 0000000 0000000 00000003211 14034043604 0016347 0 ustar 00root root 0000000 0000000 '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)
});
});
});
snapdragon-0.12.1/test/parse.js 0000664 0000000 0000000 00000011557 14034043604 0016365 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Snapdragon = require('..');
var Parser = require('../lib/parser');
var parser;
describe('parser', function() {
beforeEach(function() {
parser = new Parser();
});
describe('errors', function(cb) {
it('should throw an error when invalid args are passed to parse', function(cb) {
var parser = new Parser();
try {
parser.parse();
cb(new Error('expected an error'));
} catch (err) {
assert(err);
assert.equal(err.message, 'expected a string');
cb();
}
});
});
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() {
var pos = this.position();
var m = this.match(/^.*/);
if (!m) return;
return pos({
type: 'all',
val: m[0]
});
});
parser.parse('a/b');
assert(parser.parsers.hasOwnProperty('all'));
});
it('should use middleware to parse', function() {
parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
if (!m) return;
return pos({
type: 'all',
val: m[0]
});
});
parser.parse('a/b');
assert.equal(parser.parsed, 'a/b');
assert.equal(parser.input, '');
});
it('should create ast node:', function() {
parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
if (!m) return;
return pos({
type: 'all',
val: m[0]
});
});
parser.parse('a/b');
assert.equal(parser.ast.nodes.length, 3);
});
it('should be chainable:', function() {
parser
.set('text', function() {
var pos = this.position();
var m = this.match(/^\w+/);
if (!m) return;
return pos({
type: 'text',
val: m[0]
});
})
.set('slash', function() {
var pos = this.position();
var m = this.match(/^\//);
if (!m) return;
return pos({
type: 'slash',
val: m[0]
});
});
parser.parse('a/b');
assert.equal(parser.ast.nodes.length, 5);
});
});
});
describe('ast', function() {
beforeEach(function() {
parser = new Parser();
parser
.set('text', function() {
var pos = this.position();
var m = this.match(/^\w+/);
if (!m) return;
return pos({
type: 'text',
val: m[0]
});
})
.set('slash', function() {
var pos = this.position();
var m = this.match(/^\//);
if (!m) return;
return pos({
type: 'slash',
val: m[0]
});
});
});
describe('orig:', function() {
it('should add pattern to orig property', function() {
parser.parse('a/b');
assert.equal(parser.orig, 'a/b');
});
});
describe('recursion', function() {
beforeEach(function() {
parser.set('text', function() {
var pos = this.position();
var m = this.match(/^\w/);
if (!m) return;
return pos({
val: m[0]
});
});
parser.set('open', function() {
var pos = this.position();
var m = this.match(/^\{/);
if (!m) return;
return pos({
val: m[0]
});
});
parser.set('close', function() {
var pos = this.position();
var m = this.match(/^\}/);
if (!m) return;
return pos({
val: m[0]
});
});
parser.set('comma', function() {
var pos = this.position();
var m = this.match(/,/);
if (!m) return;
return pos({
val: m[0]
});
});
});
it('should set original string on `orig`', function() {
parser.parse('a{b,{c,d},e}f');
assert.equal(parser.orig, 'a{b,{c,d},e}f');
});
});
});
snapdragon-0.12.1/test/parser.js 0000664 0000000 0000000 00000004270 14034043604 0016541 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Parser = require('../lib/parser');
var parser;
describe('parser', function() {
beforeEach(function() {
parser = new Parser();
});
describe('constructor:', function() {
it('should return an instance of Parser:', function() {
assert(parser instanceof Parser);
});
});
// ensures that we catch and document API changes
describe('prototype methods:', function() {
var methods = [
'updatePosition',
'position',
'error',
'set',
'parse',
'match',
'use'
];
methods.forEach(function(method) {
it('should expose the `' + method + '` method', function() {
assert.equal(typeof parser[method], 'function');
});
});
});
describe('parsers', function() {
beforeEach(function() {
parser = new Parser();
});
describe('.set():', function() {
it('should register a named middleware', function() {
parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
if (!m) return;
return pos({
type: 'all',
val: m[0]
});
});
assert(typeof parser.parsers.all === 'function');
});
it('should expose named parsers to middleware:', function() {
var count = 0;
parser.set('word', function() {
var pos = this.position();
var m = this.match(/^\w/);
if (!m) return;
return pos({
type: 'word',
val: m[0]
});
});
parser.set('slash', function() {
var pos = this.position();
var m = this.match(/^\//);
if (!m) return;
var word = this.parsers.word();
var prev = this.prev();
var node = pos({
type: 'slash',
val: m[0]
});
if (word && word.type === 'word') {
count++;
}
prev.nodes.push(node);
prev.nodes.push(word);
});
parser.parse('a/b');
assert.equal(parser.ast.nodes.length, 5);
assert.equal(count, 1);
});
});
});
});
snapdragon-0.12.1/test/position.js 0000664 0000000 0000000 00000000366 14034043604 0017113 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Position = require('../lib/position');
describe('Position', function() {
it('should export a function', function() {
assert.equal(typeof Position, 'function');
});
});
snapdragon-0.12.1/test/snapdragon.capture.js 0000664 0000000 0000000 00000003624 14034043604 0021045 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Snapdragon = require('..');
var capture = require('snapdragon-capture');
var snapdragon;
describe('.capture (plugin usage)', function() {
beforeEach(function() {
snapdragon = new Snapdragon();
snapdragon.use(capture());
});
describe('errors', function(cb) {
it('should throw an error when invalid args are passed to parse', function(cb) {
try {
snapdragon.parse();
cb(new Error('expected an error'));
} catch (err) {
assert(err);
assert.equal(err.message, 'expected a string');
cb();
}
});
});
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, '');
});
it('should create ast node:', function() {
snapdragon.capture('all', /^.*/);
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.ast.nodes.length, 3);
});
it('should be chainable:', function() {
snapdragon.parser
.capture('text', /^\w+/)
.capture('slash', /^\//);
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.ast.nodes.length, 5);
});
});
});
describe('ast', function() {
beforeEach(function() {
snapdragon = new Snapdragon();
snapdragon.use(capture());
snapdragon
.capture('text', /^\w+/)
.capture('slash', /^\//);
});
describe('orig:', function() {
it('should add pattern to orig property', function() {
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.orig, 'a/b');
});
});
});
snapdragon-0.12.1/test/snapdragon.compile.js 0000664 0000000 0000000 00000003007 14034043604 0021025 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Snapdragon = require('..');
var snapdragon;
var parser;
describe('snapdragon.compiler', function() {
beforeEach(function() {
snapdragon = new Snapdragon();
snapdragon.parser
.set('text', function() {
var pos = this.position();
var match = this.match(/^\w+/);
if (match) {
return pos(this.node(match[0]));
}
})
.set('slash', function() {
var pos = this.position();
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 = snapdragon.parse('a/b/c');
snapdragon.compile(ast);
cb(new Error('expected an error'));
} catch (err) {
assert(err);
assert.equal(err.message, 'string : compiler "text" is not registered');
cb();
}
});
});
describe('snapdragon.compile', function() {
beforeEach(function() {
snapdragon.compiler
.set('text', function(node) {
return this.emit(node.val);
})
.set('slash', function(node) {
return this.emit('-');
});
});
it('should set the result on `output`', function() {
var ast = snapdragon.parse('a/b/c');
var res = snapdragon.compile(ast);
assert.equal(res.output, 'a-b-c');
});
});
});
snapdragon-0.12.1/test/snapdragon.options.js 0000664 0000000 0000000 00000001004 14034043604 0021063 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Snapdragon = require('..');
describe('.options', function() {
it('should correctly accept and store options in constructor', function() {
var snap = new Snapdragon({
a: true,
b: null,
c: false,
d: 'd'
});
assert.strictEqual(snap.options['a'], true);
assert.strictEqual(snap.options['b'], null);
assert.strictEqual(snap.options['c'], false);
assert.strictEqual(snap.options['d'], 'd');
});
});
snapdragon-0.12.1/test/snapdragon.parse.js 0000664 0000000 0000000 00000010260 14034043604 0020506 0 ustar 00root root 0000000 0000000 'use strict';
require('mocha');
var assert = require('assert');
var Snapdragon = require('..');
var snapdragon;
describe('parser', function() {
beforeEach(function() {
snapdragon = new Snapdragon();
});
describe('errors', function(cb) {
it('should throw an error when invalid args are passed to parse', function(cb) {
try {
snapdragon.parse();
cb(new Error('expected an error'));
} catch (err) {
assert(err);
assert.equal(err.message, 'expected a string');
cb();
}
});
});
describe('.set():', function() {
it('should register middleware', function() {
snapdragon.parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
if (!m) return;
return pos({
type: 'all',
val: m[0]
});
});
snapdragon.parse('a/b');
assert(snapdragon.parsers.hasOwnProperty('all'));
});
it('should use middleware to parse', function() {
snapdragon.parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
return pos({
type: 'all',
val: m[0]
});
});
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.parsed, 'a/b');
assert.equal(snapdragon.parser.input, '');
});
it('should create ast node:', function() {
snapdragon.parser.set('all', function() {
var pos = this.position();
var m = this.match(/^.*/);
return pos({
type: 'all',
val: m[0]
});
});
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.ast.nodes.length, 3);
});
it('should be chainable:', function() {
snapdragon.parser
.set('text', function() {
var pos = this.position();
var m = this.match(/^\w+/);
if (!m) return;
return pos({
type: 'text',
val: m[0]
});
})
.set('slash', function() {
var pos = this.position();
var m = this.match(/^\//);
if (!m) return;
return pos({
type: 'slash',
val: m[0]
});
});
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.ast.nodes.length, 5);
});
});
});
describe('ast', function() {
beforeEach(function() {
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]
});
})
.set('slash', function() {
var pos = this.position();
var m = this.match(/^\//);
if (!m) return;
return pos({
type: 'slash',
val: m[0]
});
});
});
describe('orig:', function() {
it('should add pattern to orig property', function() {
snapdragon.parse('a/b');
assert.equal(snapdragon.parser.orig, 'a/b');
});
});
describe('recursion', function() {
beforeEach(function() {
snapdragon.parser.set('text', function() {
var pos = this.position();
var m = this.match(/^\w/);
if (!m) return;
return pos({
type: 'text',
val: m[0]
});
});
snapdragon.parser.set('open', function() {
var pos = this.position();
var m = this.match(/^{/);
if (!m) return;
return pos({
type: 'open',
val: m[0]
});
});
snapdragon.parser.set('close', function() {
var pos = this.position();
var m = this.match(/^}/);
if (!m) return;
return pos({
type: 'close',
val: m[0]
});
});
snapdragon.parser.set('comma', function() {
var pos = this.position();
var m = this.match(/,/);
if (!m) return;
return pos({
type: 'comma',
val: m[0]
});
});
});
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');
});
});
});
snapdragon-0.12.1/test/snapdragon.regex.js 0000664 0000000 0000000 00000001350 14034043604 0020506 0 ustar 00root root 0000000 0000000 'use strict';
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() {
it('should expose a regex cache with regex from registered parsers', function() {
snapdragon.capture('dot', /^\./);
snapdragon.capture('text', /^\w+/);
snapdragon.capture('all', /^.+/);
assert(snapdragon.regex.__data__.hasOwnProperty('dot'));
assert(snapdragon.regex.__data__.hasOwnProperty('all'));
assert(snapdragon.regex.__data__.hasOwnProperty('text'));
});
});
});
snapdragon-0.12.1/verbfile.js 0000664 0000000 0000000 00000000733 14034043604 0016064 0 ustar 00root root 0000000 0000000 'use strict';
module.exports = function(verb) {
verb.use(require('verb-generate-readme'));
verb.preLayout(/\.md$/, function(file, next) {
if (!/(verb|readme)/.test(file.stem)) {
file.layout = null;
}
next();
});
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']);
};