├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .travis.yml
├── .verb.md
├── LICENSE
├── README.md
├── docs
├── compiling.md
├── core-concepts.md
├── crash-course.md
├── getting-started.md
├── options.md
├── overview.md
├── parsing.md
└── plugins.md
├── examples
├── dot.js
├── errors.js
├── guide-examples.js
├── parser.js
└── tiny-globs.js
├── gulpfile.js
├── index.js
├── lib
├── compiler.js
├── error.js
├── parser.js
├── position.js
└── source-maps.js
├── package.json
├── support
└── src
│ └── content
│ ├── compiling.md
│ ├── core-concepts.md
│ ├── crash-course.md
│ ├── getting-started.md
│ ├── guides
│ └── creating-your-first-parser.md
│ ├── options.md
│ ├── overview.md
│ ├── parsing.md
│ └── plugins.md
├── test
├── compile.js
├── compiler.js
├── nodes.js
├── parse.js
├── parser.js
├── position.js
├── snapdragon.capture.js
├── snapdragon.compile.js
├── snapdragon.options.js
├── snapdragon.parse.js
└── snapdragon.regex.js
└── verbfile.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [{**/{actual,fixtures,expected,templates}/**,*.md}]
13 | trim_trailing_whitespace = false
14 | insert_final_newline = false
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended"
4 | ],
5 |
6 | "env": {
7 | "browser": false,
8 | "es6": true,
9 | "node": true,
10 | "mocha": true
11 | },
12 |
13 | "parserOptions":{
14 | "ecmaVersion": 9,
15 | "sourceType": "module",
16 | "ecmaFeatures": {
17 | "modules": true,
18 | "experimentalObjectRestSpread": true
19 | }
20 | },
21 |
22 | "globals": {
23 | "document": false,
24 | "navigator": false,
25 | "window": false
26 | },
27 |
28 | "rules": {
29 | "accessor-pairs": 2,
30 | "arrow-spacing": [2, { "before": true, "after": true }],
31 | "block-spacing": [2, "always"],
32 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
33 | "comma-dangle": [2, "never"],
34 | "comma-spacing": [2, { "before": false, "after": true }],
35 | "comma-style": [2, "last"],
36 | "constructor-super": 2,
37 | "curly": [2, "multi-line"],
38 | "dot-location": [2, "property"],
39 | "eol-last": 2,
40 | "eqeqeq": [2, "allow-null"],
41 | "generator-star-spacing": [2, { "before": true, "after": true }],
42 | "handle-callback-err": [2, "^(err|error)$" ],
43 | "indent": [2, 2, { "SwitchCase": 1 }],
44 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
45 | "keyword-spacing": [2, { "before": true, "after": true }],
46 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
47 | "new-parens": 2,
48 | "no-array-constructor": 2,
49 | "no-caller": 2,
50 | "no-class-assign": 2,
51 | "no-cond-assign": 2,
52 | "no-const-assign": 2,
53 | "no-control-regex": 2,
54 | "no-debugger": 2,
55 | "no-delete-var": 2,
56 | "no-dupe-args": 2,
57 | "no-dupe-class-members": 2,
58 | "no-dupe-keys": 2,
59 | "no-duplicate-case": 2,
60 | "no-empty-character-class": 2,
61 | "no-eval": 2,
62 | "no-ex-assign": 2,
63 | "no-extend-native": 2,
64 | "no-extra-bind": 2,
65 | "no-extra-boolean-cast": 2,
66 | "no-extra-parens": [2, "functions"],
67 | "no-fallthrough": 2,
68 | "no-floating-decimal": 2,
69 | "no-func-assign": 2,
70 | "no-implied-eval": 2,
71 | "no-inner-declarations": [2, "functions"],
72 | "no-invalid-regexp": 2,
73 | "no-irregular-whitespace": 2,
74 | "no-iterator": 2,
75 | "no-label-var": 2,
76 | "no-labels": 2,
77 | "no-lone-blocks": 2,
78 | "no-mixed-spaces-and-tabs": 2,
79 | "no-multi-spaces": 2,
80 | "no-multi-str": 2,
81 | "no-multiple-empty-lines": [2, { "max": 1 }],
82 | "no-native-reassign": 0,
83 | "no-negated-in-lhs": 2,
84 | "no-new": 2,
85 | "no-new-func": 2,
86 | "no-new-object": 2,
87 | "no-new-require": 2,
88 | "no-new-wrappers": 2,
89 | "no-obj-calls": 2,
90 | "no-octal": 2,
91 | "no-octal-escape": 2,
92 | "no-proto": 0,
93 | "no-redeclare": 2,
94 | "no-regex-spaces": 2,
95 | "no-return-assign": 2,
96 | "no-self-compare": 2,
97 | "no-sequences": 2,
98 | "no-shadow-restricted-names": 2,
99 | "no-spaced-func": 2,
100 | "no-sparse-arrays": 2,
101 | "no-this-before-super": 2,
102 | "no-throw-literal": 2,
103 | "no-trailing-spaces": 0,
104 | "no-undef": 2,
105 | "no-undef-init": 2,
106 | "no-unexpected-multiline": 2,
107 | "no-unneeded-ternary": [2, { "defaultAssignment": false }],
108 | "no-unreachable": 2,
109 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
110 | "no-useless-call": 0,
111 | "no-with": 2,
112 | "one-var": [0, { "initialized": "never" }],
113 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
114 | "padded-blocks": [0, "never"],
115 | "quotes": [2, "single", "avoid-escape"],
116 | "radix": 2,
117 | "semi": [2, "always"],
118 | "semi-spacing": [2, { "before": false, "after": true }],
119 | "space-before-blocks": [2, "always"],
120 | "space-before-function-paren": [2, "never"],
121 | "space-in-parens": [2, "never"],
122 | "space-infix-ops": 2,
123 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
124 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
125 | "use-isnan": 2,
126 | "valid-typeof": 2,
127 | "wrap-iife": [2, "any"],
128 | "yoda": [2, "never"]
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.* text eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # always ignore files
2 | *.DS_Store
3 | .idea
4 | .vscode
5 | *.sublime-*
6 |
7 | # test related, or directories generated by tests
8 | test/actual
9 | actual
10 | coverage
11 | .nyc*
12 |
13 | # npm
14 | node_modules
15 | npm-debug.log
16 |
17 | # yarn
18 | yarn.lock
19 | yarn-error.log
20 |
21 | # misc
22 | _gh_pages
23 | _draft
24 | _drafts
25 | bower_components
26 | vendor
27 | temp
28 | tmp
29 | TODO.md
30 | package-lock.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | os:
3 | - linux
4 | - osx
5 | language: node_js
6 | node_js:
7 | - node
8 | - '14'
9 | - '12'
10 | - '10'
11 | - '9'
12 | - '8'
13 | - '7'
14 | - '6'
15 | - '5'
16 | - '4'
17 | - '0.12'
18 | - '0.10'
19 |
--------------------------------------------------------------------------------
/.verb.md:
--------------------------------------------------------------------------------
1 | Created by [jonschlinkert]({%= author.url %}) and [doowb](https://github.com/doowb).
2 |
3 | **Features**
4 |
5 | - Bootstrap your own parser, get sourcemap support for free
6 | - All parsing and compiling is handled by simple, reusable middleware functions
7 | - Inspired by the parsers in [pug][] and [css][].
8 |
9 | ## Quickstart example
10 |
11 | All of the examples in this document assume the following two lines of setup code exist first:
12 |
13 | ```js
14 | var Snapdragon = require('{%= name %}');
15 | var snapdragon = new Snapdragon();
16 | ```
17 |
18 | **Parse a string**
19 |
20 | ```js
21 | var ast = snapdragon.parser
22 | // parser handlers (essentially middleware)
23 | // used for parsing substrings to create tokens
24 | .set('foo', function () {})
25 | .set('bar', function () {})
26 | .parse('some string', options);
27 | ```
28 |
29 | **Compile an AST returned from `.parse()`**
30 |
31 | ```js
32 | var result = snapdragon.compiler
33 | // compiler handlers (essentially middleware),
34 | // called on a node when the `node.type` matches
35 | // the name of the handler
36 | .set('foo', function () {})
37 | .set('bar', function () {})
38 | // pass the `ast` from the parse method
39 | .compile(ast)
40 |
41 | // the compiled string
42 | console.log(result.output);
43 | ```
44 |
45 | See the [examples](./examples/).
46 |
47 | ## Parsing
48 |
49 | **Parser handlers**
50 |
51 | Parser handlers are middleware functions responsible for matching substrings to create tokens:
52 |
53 | **Example handler**
54 |
55 | ```js
56 | var ast = snapdragon.parser
57 | .set('dot', function() {
58 | var pos = this.position();
59 | var m = this.match(/^\./);
60 | if (!m) return;
61 | return pos({
62 | // the "type" will be used by the compiler later on,
63 | // we'll go over this in the compiler docs
64 | type: 'dot',
65 | // "val" is the string captured by ".match",
66 | // in this case that would be '.'
67 | val: m[0]
68 | });
69 | })
70 | .parse('.'[, options])
71 | ```
72 |
73 | _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._
74 |
75 | **Example token**
76 |
77 | And the resulting tokens look something like this:
78 |
79 | ```js
80 | {
81 | type: 'dot',
82 | val: '.'
83 | }
84 | ```
85 |
86 | **Position**
87 |
88 | Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured:
89 |
90 | ```js
91 | { type: 'dot',
92 | val: '.',
93 | position:
94 | { start: { lineno: 1, column: 1 },
95 | end: { lineno: 1, column: 2 } }}
96 | ```
97 |
98 | **Life as an AST node**
99 |
100 | 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.
101 |
102 |
103 | **Wrapping up**
104 |
105 | In the parser calls all handlers and cannot find a match for a substring, an error is thrown.
106 |
107 | Assuming the parser finished parsing the entire string, an AST is returned.
108 |
109 |
110 | ## Compiling
111 |
112 | 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`.
113 |
114 | This function is called a "handler".
115 |
116 | **Compiler handlers**
117 |
118 | Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler.
119 |
120 | ```js
121 | var result = snapdragon.compiler
122 | .set('dot', function (node) {
123 | console.log(node.val)
124 | //=> '.'
125 | return this.emit(node.val);
126 | })
127 | ```
128 |
129 | If `node.type` does not match a registered handler, an error is thrown.
130 |
131 |
132 | **Source maps**
133 |
134 | 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`).
135 |
136 | ```js
137 | var res = snapdragon.compiler
138 | .set('dot', function (node) {
139 | return this.emit(node.val, node);
140 | })
141 | ```
142 |
143 | ## All together
144 |
145 | This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
146 |
147 | ```js
148 | var Snapdragon = require('..');
149 | var snapdragon = new Snapdragon();
150 |
151 | var ast = snapdragon.parser
152 | .set('dot', function () {
153 | var pos = this.position();
154 | var m = this.match(/^\./);
155 | if (!m) return;
156 | return pos({
157 | type: 'dot',
158 | val: m[0]
159 | })
160 | })
161 | .parse('.')
162 |
163 | var result = snapdragon.compiler
164 | .set('dot', function (node) {
165 | return this.emit('\\' + node.val);
166 | })
167 | .compile(ast)
168 |
169 | console.log(result.output);
170 | //=> '\.'
171 | ```
172 |
173 | ## API
174 |
175 | ### Parse
176 | {%= apidocs("lib/parser.js") %}
177 |
178 | ### Compile
179 | {%= apidocs("lib/compiler.js") %}
180 |
181 |
182 | ## Snapdragon in the wild
183 | {%= verb.related.description %}
184 | {%= related(verb.related.implementations) %}
185 |
186 | ## History
187 |
188 | ### v0.9.0
189 |
190 | **Breaking changes!**
191 |
192 | In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release.
193 |
194 | - `parser.capture` was externalized to [snapdragon-capture][]
195 | - `parser.capturePair` was externalized to [snapdragon-capture-set][]
196 | - Nodes are now an instance of [snapdragon-node][]
197 |
198 | ### v0.5.0
199 |
200 | **Breaking changes!**
201 |
202 | 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.
203 |
204 | - Renderer was renamed to `Compiler`
205 | - the `.render` method was renamed to `.compile`
206 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-2018, Jon Schlinkert.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # snapdragon [](https://www.npmjs.com/package/snapdragon) [](https://npmjs.org/package/snapdragon) [](https://npmjs.org/package/snapdragon) [](https://travis-ci.org/here-be/snapdragon)
2 |
3 | > Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.
4 |
5 | Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
6 |
7 | ## Table of Contents
8 |
9 |
10 | Details
11 |
12 | - [Install](#install)
13 | - [Quickstart example](#quickstart-example)
14 | - [Parsing](#parsing)
15 | - [Compiling](#compiling)
16 | - [All together](#all-together)
17 | - [API](#api)
18 | * [Parse](#parse)
19 | * [Compile](#compile)
20 | - [Snapdragon in the wild](#snapdragon-in-the-wild)
21 | - [History](#history)
22 | * [v0.9.0](#v090)
23 | * [v0.5.0](#v050)
24 | - [About](#about)
25 |
26 |
27 |
28 | ## Install
29 |
30 | Install with [npm](https://www.npmjs.com/):
31 |
32 | ```sh
33 | $ npm install --save snapdragon
34 | ```
35 |
36 | Created by [jonschlinkert](https://github.com/jonschlinkert) and [doowb](https://github.com/doowb).
37 |
38 | **Features**
39 |
40 | * Bootstrap your own parser, get sourcemap support for free
41 | * All parsing and compiling is handled by simple, reusable middleware functions
42 | * Inspired by the parsers in [pug](https://pugjs.org/) and [css](https://github.com/reworkcss/css).
43 |
44 | ## Quickstart example
45 |
46 | All of the examples in this document assume the following two lines of setup code exist first:
47 |
48 | ```js
49 | var Snapdragon = require('snapdragon');
50 | var snapdragon = new Snapdragon();
51 | ```
52 |
53 | **Parse a string**
54 |
55 | ```js
56 | var ast = snapdragon.parser
57 | // parser handlers (essentially middleware)
58 | // used for parsing substrings to create tokens
59 | .set('foo', function () {})
60 | .set('bar', function () {})
61 | .parse('some string', options);
62 | ```
63 |
64 | **Compile an AST returned from `.parse()`**
65 |
66 | ```js
67 | var result = snapdragon.compiler
68 | // compiler handlers (essentially middleware),
69 | // called on a node when the `node.type` matches
70 | // the name of the handler
71 | .set('foo', function () {})
72 | .set('bar', function () {})
73 | // pass the `ast` from the parse method
74 | .compile(ast)
75 |
76 | // the compiled string
77 | console.log(result.output);
78 | ```
79 |
80 | See the [examples](./examples/).
81 |
82 | ## Parsing
83 |
84 | **Parser handlers**
85 |
86 | Parser handlers are middleware functions responsible for matching substrings to create tokens:
87 |
88 | **Example handler**
89 |
90 | ```js
91 | var ast = snapdragon.parser
92 | .set('dot', function() {
93 | var pos = this.position();
94 | var m = this.match(/^\./);
95 | if (!m) return;
96 | return pos({
97 | // the "type" will be used by the compiler later on,
98 | // we'll go over this in the compiler docs
99 | type: 'dot',
100 | // "val" is the string captured by ".match",
101 | // in this case that would be '.'
102 | val: m[0]
103 | });
104 | })
105 | .parse('.'[, options])
106 | ```
107 |
108 | _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._
109 |
110 | **Example token**
111 |
112 | And the resulting tokens look something like this:
113 |
114 | ```js
115 | {
116 | type: 'dot',
117 | val: '.'
118 | }
119 | ```
120 |
121 | **Position**
122 |
123 | Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured:
124 |
125 | ```js
126 | { type: 'dot',
127 | val: '.',
128 | position:
129 | { start: { lineno: 1, column: 1 },
130 | end: { lineno: 1, column: 2 } }}
131 | ```
132 |
133 | **Life as an AST node**
134 |
135 | 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.
136 |
137 | **Wrapping up**
138 |
139 | In the parser calls all handlers and cannot find a match for a substring, an error is thrown.
140 |
141 | Assuming the parser finished parsing the entire string, an AST is returned.
142 |
143 | ## Compiling
144 |
145 | 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`.
146 |
147 | This function is called a "handler".
148 |
149 | **Compiler handlers**
150 |
151 | Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler.
152 |
153 | ```js
154 | var result = snapdragon.compiler
155 | .set('dot', function (node) {
156 | console.log(node.val)
157 | //=> '.'
158 | return this.emit(node.val);
159 | })
160 | ```
161 |
162 | If `node.type` does not match a registered handler, an error is thrown.
163 |
164 | **Source maps**
165 |
166 | 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`).
167 |
168 | ```js
169 | var res = snapdragon.compiler
170 | .set('dot', function (node) {
171 | return this.emit(node.val, node);
172 | })
173 | ```
174 |
175 | ## All together
176 |
177 | This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot.
178 |
179 | ```js
180 | var Snapdragon = require('..');
181 | var snapdragon = new Snapdragon();
182 |
183 | var ast = snapdragon.parser
184 | .set('dot', function () {
185 | var pos = this.position();
186 | var m = this.match(/^\./);
187 | if (!m) return;
188 | return pos({
189 | type: 'dot',
190 | val: m[0]
191 | })
192 | })
193 | .parse('.')
194 |
195 | var result = snapdragon.compiler
196 | .set('dot', function (node) {
197 | return this.emit('\\' + node.val);
198 | })
199 | .compile(ast)
200 |
201 | console.log(result.output);
202 | //=> '\.'
203 | ```
204 |
205 | ## API
206 |
207 | ### [Parser](lib/parser.js#L27)
208 |
209 | Create a new `Parser` with the given `input` and `options`.
210 |
211 | **Params**
212 |
213 | * `input` **{String}**
214 | * `options` **{Object}**
215 |
216 | **Example**
217 |
218 | ```js
219 | var Snapdragon = require('snapdragon');
220 | var Parser = Snapdragon.Parser;
221 | var parser = new Parser();
222 | ```
223 |
224 | ### [.error](lib/parser.js#L97)
225 |
226 | Throw a formatted error message with details including the cursor position.
227 |
228 | **Params**
229 |
230 | * `msg` **{String}**: Message to use in the Error.
231 | * `node` **{Object}**
232 | * `returns` **{undefined}**
233 |
234 | **Example**
235 |
236 | ```js
237 | parser.set('foo', function(node) {
238 | if (node.val !== 'foo') {
239 | throw this.error('expected node.val to be "foo"', node);
240 | }
241 | });
242 | ```
243 |
244 | ### [.define](lib/parser.js#L115)
245 |
246 | Define a non-enumberable property on the `Parser` instance. This is useful in plugins, for exposing methods inside handlers.
247 |
248 | **Params**
249 |
250 | * `key` **{String}**: propery name
251 | * `val` **{any}**: property value
252 | * `returns` **{Object}**: Returns the Parser instance for chaining.
253 |
254 | **Example**
255 |
256 | ```js
257 | parser.define('foo', 'bar');
258 | ```
259 |
260 | ### [.node](lib/parser.js#L133)
261 |
262 | Create a new [Node](#node) with the given `val` and `type`.
263 |
264 | **Params**
265 |
266 | * `val` **{Object}**
267 | * `type` **{String}**
268 | * `returns` **{Object}**: returns the [Node](#node) instance.
269 |
270 | **Example**
271 |
272 | ```js
273 | parser.node('/', 'slash');
274 | ```
275 |
276 | ### [.position](lib/parser.js#L155)
277 |
278 | Mark position and patch `node.position`.
279 |
280 | * `returns` **{Function}**: Returns a function that takes a `node`
281 |
282 | **Example**
283 |
284 | ```js
285 | parser.set('foo', function(node) {
286 | var pos = this.position();
287 | var match = this.match(/foo/);
288 | if (match) {
289 | // call `pos` with the node
290 | return pos(this.node(match[0]));
291 | }
292 | });
293 | ```
294 |
295 | ### [.set](lib/parser.js#L187)
296 |
297 | Add parser `type` with the given visitor `fn`.
298 |
299 | **Params**
300 |
301 | * `type` **{String}**
302 | * `fn` **{Function}**
303 |
304 | **Example**
305 |
306 | ```js
307 | parser.set('all', function() {
308 | var match = this.match(/^./);
309 | if (match) {
310 | return this.node(match[0]);
311 | }
312 | });
313 | ```
314 |
315 | ### [.get](lib/parser.js#L206)
316 |
317 | Get parser `type`.
318 |
319 | **Params**
320 |
321 | * `type` **{String}**
322 |
323 | **Example**
324 |
325 | ```js
326 | var fn = parser.get('slash');
327 | ```
328 |
329 | ### [.push](lib/parser.js#L229)
330 |
331 | Push a node onto the stack for the given `type`.
332 |
333 | **Params**
334 |
335 | * `type` **{String}**
336 | * `returns` **{Object}** `token`
337 |
338 | **Example**
339 |
340 | ```js
341 | parser.set('all', function() {
342 | var match = this.match(/^./);
343 | if (match) {
344 | var node = this.node(match[0]);
345 | this.push(node);
346 | return node;
347 | }
348 | });
349 | ```
350 |
351 | ### [.pop](lib/parser.js#L261)
352 |
353 | Pop a token off of the stack of the given `type`.
354 |
355 | **Params**
356 |
357 | * `type` **{String}**
358 | * `returns` **{Object}**: Returns a token
359 |
360 | **Example**
361 |
362 | ```js
363 | parser.set('close', function() {
364 | var match = this.match(/^\}/);
365 | if (match) {
366 | var node = this.node({
367 | type: 'close',
368 | val: match[0]
369 | });
370 |
371 | this.pop(node.type);
372 | return node;
373 | }
374 | });
375 | ```
376 |
377 | ### [.isInside](lib/parser.js#L294)
378 |
379 | 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.
380 |
381 | **Params**
382 |
383 | * `type` **{String}**
384 | * `returns` **{Boolean}**
385 |
386 | **Example**
387 |
388 | ```js
389 | parser.set('close', function() {
390 | var pos = this.position();
391 | var m = this.match(/^\}/);
392 | if (!m) return;
393 | if (!this.isInside('bracket')) {
394 | throw new Error('missing opening bracket');
395 | }
396 | });
397 | ```
398 |
399 | ### [.isType](lib/parser.js#L324)
400 |
401 | Return true if `node` is the given `type`.
402 |
403 | **Params**
404 |
405 | * `node` **{Object}**
406 | * `type` **{String}**
407 | * `returns` **{Boolean}**
408 |
409 | **Example**
410 |
411 | ```js
412 | parser.isType(node, 'brace');
413 | ```
414 |
415 | ### [.prev](lib/parser.js#L340)
416 |
417 | Get the previous AST node from the `parser.stack` (when inside a nested context) or `parser.nodes`.
418 |
419 | * `returns` **{Object}**
420 |
421 | **Example**
422 |
423 | ```js
424 | var prev = this.prev();
425 | ```
426 |
427 | ### [.prev](lib/parser.js#L394)
428 |
429 | Match `regex`, return captures, and update the cursor position by `match[0]` length.
430 |
431 | **Params**
432 |
433 | * `regex` **{RegExp}**
434 | * `returns` **{Object}**
435 |
436 | **Example**
437 |
438 | ```js
439 | // make sure to use the starting regex boundary: "^"
440 | var match = this.match(/^\./);
441 | ```
442 |
443 | **Params**
444 |
445 | * `input` **{String}**
446 | * `returns` **{Object}**: Returns an AST with `ast.nodes`
447 |
448 | **Example**
449 |
450 | ```js
451 | var ast = parser.parse('foo/bar');
452 | ```
453 |
454 | ### [Compiler](lib/compiler.js#L24)
455 |
456 | Create a new `Compiler` with the given `options`.
457 |
458 | **Params**
459 |
460 | * `options` **{Object}**
461 | * `state` **{Object}**: Optionally pass a "state" object to use inside visitor functions.
462 |
463 | **Example**
464 |
465 | ```js
466 | var Snapdragon = require('snapdragon');
467 | var Compiler = Snapdragon.Compiler;
468 | var compiler = new Compiler();
469 | ```
470 |
471 | ### [.error](lib/compiler.js#L67)
472 |
473 | Throw a formatted error message with details including the cursor position.
474 |
475 | **Params**
476 |
477 | * `msg` **{String}**: Message to use in the Error.
478 | * `node` **{Object}**
479 | * `returns` **{undefined}**
480 |
481 | **Example**
482 |
483 | ```js
484 | compiler.set('foo', function(node) {
485 | if (node.val !== 'foo') {
486 | throw this.error('expected node.val to be "foo"', node);
487 | }
488 | });
489 | ```
490 |
491 | ### [.emit](lib/compiler.js#L86)
492 |
493 | Concat the given string to `compiler.output`.
494 |
495 | **Params**
496 |
497 | * `string` **{String}**
498 | * `node` **{Object}**: Optionally pass the node to use for position if source maps are enabled.
499 | * `returns` **{String}**: returns the string
500 |
501 | **Example**
502 |
503 | ```js
504 | compiler.set('foo', function(node) {
505 | this.emit(node.val, node);
506 | });
507 | ```
508 |
509 | ### [.noop](lib/compiler.js#L104)
510 |
511 | Emit an empty string to effectively "skip" the string for the given `node`, but still emit the position and node type.
512 |
513 | **Params**
514 |
515 | * **{Object}**: node
516 |
517 | **Example**
518 |
519 | ```js
520 | // example: do nothing for beginning-of-string
521 | snapdragon.compiler.set('bos', compiler.noop);
522 | ```
523 |
524 | ### [.define](lib/compiler.js#L124)
525 |
526 | Define a non-enumberable property on the `Compiler` instance. This is useful in plugins, for exposing methods inside handlers.
527 |
528 | **Params**
529 |
530 | * `key` **{String}**: propery name
531 | * `val` **{any}**: property value
532 | * `returns` **{Object}**: Returns the Compiler instance for chaining.
533 |
534 | **Example**
535 |
536 | ```js
537 | compiler.define('customMethod', function() {
538 | // do stuff
539 | });
540 | ```
541 |
542 | ### [.set](lib/compiler.js#L152)
543 |
544 | 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.
545 |
546 | **Params**
547 |
548 | * `type` **{String}**
549 | * `fn` **{Function}**
550 |
551 | **Example**
552 |
553 | ```js
554 | compiler
555 | .set('comma', function(node) {
556 | this.emit(',');
557 | })
558 | .set('dot', function(node) {
559 | this.emit('.');
560 | })
561 | .set('slash', function(node) {
562 | this.emit('/');
563 | });
564 | ```
565 |
566 | ### [.get](lib/compiler.js#L168)
567 |
568 | Get the compiler of the given `type`.
569 |
570 | **Params**
571 |
572 | * `type` **{String}**
573 |
574 | **Example**
575 |
576 | ```js
577 | var fn = compiler.get('slash');
578 | ```
579 |
580 | ### [.visit](lib/compiler.js#L188)
581 |
582 | Visit `node` using the registered compiler function associated with the `node.type`.
583 |
584 | **Params**
585 |
586 | * `node` **{Object}**
587 | * `returns` **{Object}**: returns the node
588 |
589 | **Example**
590 |
591 | ```js
592 | compiler
593 | .set('i', function(node) {
594 | this.visit(node);
595 | })
596 | ```
597 |
598 | ### [.mapVisit](lib/compiler.js#L226)
599 |
600 | Iterate over `node.nodes`, calling [visit](#visit) on each node.
601 |
602 | **Params**
603 |
604 | * `node` **{Object}**
605 | * `returns` **{Object}**: returns the node
606 |
607 | **Example**
608 |
609 | ```js
610 | compiler
611 | .set('i', function(node) {
612 | utils.mapVisit(node);
613 | })
614 | ```
615 |
616 | ### [.compile](lib/compiler.js#L250)
617 |
618 | Compile the given `AST` and return a string. Iterates over `ast.nodes` with [mapVisit](#mapVisit).
619 |
620 | **Params**
621 |
622 | * `ast` **{Object}**
623 | * `options` **{Object}**: Compiler options
624 | * `returns` **{Object}**: returns the node
625 |
626 | **Example**
627 |
628 | ```js
629 | var ast = parser.parse('foo');
630 | var str = compiler.compile(ast);
631 | ```
632 |
633 | ## Snapdragon in the wild
634 |
635 | A few of the libraries that use snapdragon:
636 |
637 | * [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.")
638 | * [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.")
639 | * [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.")
640 | * [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.")
641 | * [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.")
642 | * [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)")
643 |
644 | ## History
645 |
646 | ### v0.9.0
647 |
648 | **Breaking changes!**
649 |
650 | In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release.
651 |
652 | * `parser.capture` was externalized to [snapdragon-capture](https://github.com/jonschlinkert/snapdragon-capture)
653 | * `parser.capturePair` was externalized to [snapdragon-capture-set](https://github.com/jonschlinkert/snapdragon-capture-set)
654 | * Nodes are now an instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
655 |
656 | ### v0.5.0
657 |
658 | **Breaking changes!**
659 |
660 | 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.
661 |
662 | * Renderer was renamed to `Compiler`
663 | * the `.render` method was renamed to `.compile`
664 |
665 | ## About
666 |
667 |
668 | Contributing
669 |
670 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
671 |
672 |
673 |
674 |
675 | Running Tests
676 |
677 | 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:
678 |
679 | ```sh
680 | $ npm install && npm test
681 | ```
682 |
683 |
684 |
685 |
686 | Building docs
687 |
688 | _(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.)_
689 |
690 | To generate the readme, run the following command:
691 |
692 | ```sh
693 | $ npm install -g verbose/verb#dev verb-generate-readme && verb
694 | ```
695 |
696 |
697 |
698 | ### Related projects
699 |
700 | A few of the libraries that use snapdragon:
701 |
702 | * [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")
703 | * [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.")
704 | * [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.")
705 | * [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.")
706 |
707 | ### Contributors
708 |
709 | | **Commits** | **Contributor** |
710 | | --- | --- |
711 | | 156 | [jonschlinkert](https://github.com/jonschlinkert) |
712 | | 3 | [doowb](https://github.com/doowb) |
713 | | 2 | [danez](https://github.com/danez) |
714 | | 1 | [EdwardBetts](https://github.com/EdwardBetts) |
715 |
716 | ### Author
717 |
718 | **Jon Schlinkert**
719 |
720 | * [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
721 | * [GitHub Profile](https://github.com/jonschlinkert)
722 | * [Twitter Profile](https://twitter.com/jonschlinkert)
723 |
724 | ### License
725 |
726 | Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).
727 | Released under the [MIT License](LICENSE).
728 |
729 | ***
730 |
731 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on March 20, 2018._
--------------------------------------------------------------------------------
/docs/compiling.md:
--------------------------------------------------------------------------------
1 | # Compiling with snapdragon
2 |
3 |
4 | Pre-requisites
5 | 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.
6 |
7 | 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!
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/core-concepts.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Core concepts
4 |
5 | - [Lexer](#parser)
6 | * Token Stream
7 | * Token
8 | * Scope
9 | - [Parser](#parser)
10 | * [Node](#node)
11 | * Stack
12 | * [AST](#ast)
13 | - [Compiler](#compiler)
14 | * State
15 | - [Renderer](#renderer)
16 | * Contexts
17 | * Context
18 |
19 | ## Lexer
20 |
21 | - [ ] Token
22 | - [ ] Tokens
23 | - [ ] Scope
24 |
25 | ## Parser
26 |
27 | ### AST
28 |
29 | TODO
30 |
31 | ### Node
32 |
33 | #### Properties
34 |
35 | Officially supported properties
36 |
37 | - `type`
38 | - `val`
39 | - `nodes`
40 |
41 | **Related**
42 |
43 | - The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value.
44 | - The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node.
45 |
46 | ## Compiler
47 |
48 | TODO
49 |
50 | ## Renderer
51 |
52 | TODO
53 |
54 | [verb][]
55 |
--------------------------------------------------------------------------------
/docs/crash-course.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | ## Crash course
4 |
5 | ### Parser
6 |
7 | 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.
8 |
9 | **Parsing**
10 |
11 | When a middleware returns a node, the parser updates the string position and starts over again with the first middleware.
12 |
13 | **Parser middleware**
14 |
15 | 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.
16 |
17 | **Node**
18 |
19 | 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.
20 |
21 | The only required property is `node.type`.
22 |
23 | Every node has a `node.type` that
24 |
25 | 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.
26 |
27 | of a specific `node.type` that semantically describes the capturing substrings
28 | . Matching is typically performed using a regular expression, but any means can be used.
29 |
30 | Upon capturing a substring, the parser-middleware
31 |
32 | - capturing and/or further processing relevant part(s) of the captured substring
33 | - returning a node with information that semantically describes the substring that was captured, along with
34 |
35 | When a parser returns a node, that indicates
36 |
37 | by calling each user-defined middleware (referred to as "parsers") until one returns a node.
38 | Each parser middleware
39 | middleware
40 | a string and calling user-defined "parsers"
41 |
42 | **AST**
43 |
44 | which is an object with "nodes", where each "node" is an object with a `type`
45 |
46 | **Nodes**
47 |
48 | A `node` is an object that is used for storing and describing information about a captured substring.
49 |
50 | Every node in the AST has a `type` property, and either:
51 |
52 | - `val`: a captured substring
53 | - `nodes`: an array of child nodes
54 |
55 | When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will
56 |
57 | In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST.
58 |
59 | **Example**
60 |
61 | The absolute simplest AST for a single-character string might look something like this:
62 |
63 | ```js
64 | var ast = {
65 | type: 'root',
66 | nodes: [
67 | {
68 | type: 'text',
69 | val: 'a'
70 | }
71 | ]
72 | };
73 | ```
74 |
75 | Nodes may have any additional properties, but they must have
76 |
77 | Parsers and compilers have a one-to-one relationship.
78 |
79 | The parser uses middleware for
80 |
81 | 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.
82 |
83 | ### Compiler
84 |
85 | 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.
86 |
87 | **A compiler for every parser**
88 |
89 | Parsers and compilers have a one-to-one relationship.
90 |
91 | The parser uses middleware for
92 |
93 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Getting started
4 |
5 | [What is snapdragon, and who created it?](overview.html)
6 |
7 | - Installing snapdragon
8 | - Basic usage
9 | - Next steps
10 |
11 | ## Installing snapdragon
12 |
13 | ## Usage documentation
14 |
15 | **Learn how to use snapdragon**
16 |
17 | 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.
18 |
19 | is API-focused
20 | how to the API methods that are
21 |
22 | ## Developer documentation
23 |
24 | **Learn how to create plugins or hack on snapdragon**
25 |
26 | 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,
27 |
--------------------------------------------------------------------------------
/docs/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | WIP (draft)
4 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Overview
4 |
5 | Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome.
6 |
7 | ## What is snapdragon?
8 |
9 | At its heart, snapdragon does two things:
10 |
11 | - Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST
12 | - Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string.
13 |
14 | **Plugins**
15 |
16 | ## What can snapdragon do?
17 |
18 | 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.
19 |
20 | **In the wild**
21 |
22 | Here's how some real projects are using snapdragon:
23 |
24 | * [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]:
25 | * [micromatch][]: uses snapdragon to create regex from glob patterns
26 | * [extglob][]: uses snapdragon to create regex from glob patterns
27 | * [braces][]: uses snapdragon to create regex for bash-like brace-expansion
28 | * [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions)
29 |
30 | ## About
31 |
32 | Snapdragon was created by, [Jon Schlinkert](https://github.com/jonschlinkert), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects.
33 |
34 | If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to:
35 |
36 | - follow me on [github]() for notifications and updates about my github projects
37 | - follow me on [twitter]()
38 | - connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert)
39 |
--------------------------------------------------------------------------------
/docs/parsing.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Parsing with snapdragon
4 |
5 |
6 | Pre-requisites
7 | 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.
8 |
9 | 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!
10 |
11 |
12 |
13 | Table of contents
14 | - Usage
15 | - Developer
16 | * Parser
17 | * Parsers
18 | * Custom parsers
19 |
20 |
21 | ## API
22 |
23 | ## Parser
24 |
25 | The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string.
26 |
27 | To understand what `Parser` does,
28 |
29 | The snapdragon parser takes a string and creates an by
30 |
31 | 1. looping over the string
32 | 1. invoking registered [parsers](#parsers) to create new AST nodes.
33 |
34 | The following documentation describes this in more detail.
35 |
36 | checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and:
37 | * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function)
38 | * if _no matches are found_, an error is throw notifying you that the s
39 |
40 | ## Parsers
41 |
42 | Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string.
43 |
44 | **How parsers work**
45 |
46 | A very basic parser function might look something like this:
47 |
48 | ```js
49 | function() {
50 | var parsed = this.parsed;
51 | var pos = this.position();
52 | var m = this.match(regex);
53 | if (!m || !m[0]) return;
54 |
55 | var prev = this.prev();
56 | var node = pos({
57 | type: type,
58 | val: m[0]
59 | });
60 |
61 | define(node, 'match', m);
62 | define(node, 'inside', this.stack.length > 0);
63 | define(node, 'parent', prev);
64 | define(node, 'parsed', parsed);
65 | define(node, 'rest', this.input);
66 | prev.nodes.push(node);
67 | }
68 | ```
69 |
70 | TODO
71 |
72 | ## Custom parsers
73 |
74 | TODO
75 |
76 | ## Plugins
77 |
78 | TODO
79 |
80 | ```js
81 | parser.use(function() {});
82 | ```
83 |
84 | ```js
85 | snapdragon.parser.use(function() {});
86 | ```
87 |
--------------------------------------------------------------------------------
/docs/plugins.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Snapdragon plugins
4 |
5 | ```js
6 | var snapdragon = new Snapdgragon();
7 | // register plugins
8 | snapdragon.use(function() {});
9 |
10 | // register parser plugins
11 | snapdragon.parser.use(function() {});
12 |
13 | // register compiler plugins
14 | snapdragon.compiler.use(function() {});
15 |
16 | // parse
17 | var ast = snapdragon.parse('foo/bar');
18 | ```
19 |
--------------------------------------------------------------------------------
/examples/dot.js:
--------------------------------------------------------------------------------
1 | var Snapdragon = require('..');
2 | var snapdragon = new Snapdragon();
3 |
4 | var ast = snapdragon.parser
5 | .set('dot', function () {
6 | var pos = this.position();
7 | var m = this.match(/^\./);
8 | if (!m) return;
9 | return pos({
10 | // define the `type` of compiler to use
11 | // setting this value is optional, since the
12 | // parser will add it based on the name used
13 | // when registering the handler, but it's
14 | // good practice since tokens aren't always
15 | // returned
16 | type: 'dot',
17 | val: m[0]
18 | })
19 | })
20 | .parse('.')
21 |
22 | var result = snapdragon.compiler
23 | .set('dot', function (node) {
24 | return this.emit('\\' + node.val);
25 | })
26 | .compile(ast)
27 |
28 | console.log(result.output);
29 | //=> '\.'
30 |
--------------------------------------------------------------------------------
/examples/errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Parser = require('../lib/parser');
4 |
5 | var parser = new Parser()
6 | .set('at', function() {
7 | var pos = this.position();
8 | var match = this.match(/^@/);
9 | if (match) {
10 | return pos({val: match[0]});
11 | }
12 | })
13 | .set('slash', function() {
14 | var pos = this.position();
15 | var match = this.match(/^\//);
16 | if (match) {
17 | return pos({val: match[0]});
18 | }
19 | })
20 | .set('text', function() {
21 | var pos = this.position();
22 | var match = this.match(/^\w+/);
23 | if (match) {
24 | return pos({val: match[0]});
25 | }
26 | })
27 |
28 | var ast = parser.parse('git@github.com:foo/bar.git');
29 | console.log(ast);
30 |
--------------------------------------------------------------------------------
/examples/guide-examples.js:
--------------------------------------------------------------------------------
1 | var Snapdragon = require('..');
2 | var snapdragon = new Snapdragon();
3 |
4 | /**
5 | *
6 | */
7 |
8 | var ast = snapdragon.parse('foo/*.js');
9 | console.log(ast);
10 |
--------------------------------------------------------------------------------
/examples/parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Parser = require('../lib/parser');
4 |
5 | var parser = new Parser()
6 | .set('at', function() {
7 | var pos = this.position();
8 | var match = this.match(/^@/);
9 | if (match) {
10 | return pos({val: match[0]});
11 | }
12 | })
13 | .set('slash', function() {
14 | var pos = this.position();
15 | var match = this.match(/^\//);
16 | if (match) {
17 | return pos({val: match[0]});
18 | }
19 | })
20 | .set('text', function() {
21 | var pos = this.position();
22 | var match = this.match(/^\w+/);
23 | if (match) {
24 | return pos({val: match[0]});
25 | }
26 | })
27 | .set('dot', function() {
28 | var pos = this.position();
29 | var match = this.match(/^\./);
30 | if (match) {
31 | return pos({val: match[0]});
32 | }
33 | })
34 | .set('colon', function() {
35 | var pos = this.position();
36 | var match = this.match(/^:/);
37 | if (match) {
38 | return pos({val: match[0]});
39 | }
40 | })
41 |
42 | var ast = parser.parse('git@github.com:foo/bar.git');
43 | console.log(ast);
44 |
--------------------------------------------------------------------------------
/examples/tiny-globs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Snapdragon = require('..');
4 | var Snapdragon = new Snapdragon();
5 |
6 | /**
7 | * 1
8 | */
9 |
10 |
11 | // var parser = new Parser();
12 | // console.log(parser.parse('foo/*.js'));
13 |
14 |
15 | /**
16 | * 2
17 | */
18 |
19 |
20 | snapdragon.parser
21 | .set('text', function() {
22 | var pos = this.position();
23 | var m = this.match(/^\w+/);
24 | if (m) {
25 | return pos(this.node(m[0]));
26 | }
27 | })
28 | .set('slash', function() {
29 | var pos = this.position();
30 | var m = this.match(/^\//);
31 | if (m) {
32 | return pos(this.node(m[0]));
33 | }
34 | })
35 | .set('star', function() {
36 | var pos = this.position();
37 | var m = this.match(/^\*/);
38 | if (m) {
39 | return pos(this.node(m[0]));
40 | }
41 | })
42 | .set('dot', function() {
43 | var pos = this.position();
44 | var m = this.match(/^\./);
45 | if (m) {
46 | return pos(this.node(m[0]));
47 | }
48 | });
49 |
50 | console.log(parser.parse('foo/*.js'));
51 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var istanbul = require('gulp-istanbul');
5 | var eslint = require('gulp-eslint');
6 | var mocha = require('gulp-mocha');
7 | var unused = require('gulp-unused');
8 |
9 | gulp.task('coverage', function() {
10 | return gulp.src(['lib/*.js', 'index.js'])
11 | .pipe(istanbul({includeUntested: true}))
12 | .pipe(istanbul.hookRequire());
13 | });
14 |
15 | gulp.task('mocha', ['coverage'], function() {
16 | return gulp.src(['test/*.js'])
17 | .pipe(mocha())
18 | .pipe(istanbul.writeReports());
19 | });
20 |
21 | gulp.task('eslint', function() {
22 | return gulp.src(['*.js', 'lib/*.js', 'test/*.js'])
23 | .pipe(eslint())
24 | .pipe(eslint.format());
25 | });
26 |
27 | gulp.task('unused', function() {
28 | return gulp.src(['index.js', 'lib/*.js'])
29 | .pipe(unused({keys: Object.keys(require('./lib/utils.js'))}));
30 | });
31 |
32 | gulp.task('default', ['coverage', 'eslint', 'mocha']);
33 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var define = require('define-property');
4 | var extend = require('extend-shallow');
5 | var Compiler = require('./lib/compiler');
6 | var Parser = require('./lib/parser');
7 |
8 | /**
9 | * Create a new instance of `Snapdragon` with the given `options`.
10 | *
11 | * ```js
12 | * var Snapdragon = require('snapdragon');
13 | * var snapdragon = new Snapdragon();
14 | * ```
15 | * @param {Object} `options`
16 | * @api public
17 | */
18 |
19 | function Snapdragon(options) {
20 | if (typeof options === 'string') {
21 | var protoa = Object.create(Snapdragon.prototype);
22 | Snapdragon.call(protoa);
23 | return protoa.render.apply(protoa, arguments);
24 | }
25 |
26 | if (!(this instanceof Snapdragon)) {
27 | var protob = Object.create(Snapdragon.prototype);
28 | Snapdragon.call(protob);
29 | return protob;
30 | }
31 |
32 | this.define('cache', {});
33 | this.options = extend({source: 'string'}, options);
34 | this.isSnapdragon = true;
35 | this.plugins = {
36 | fns: [],
37 | preprocess: [],
38 | visitors: {},
39 | before: {},
40 | after: {}
41 | };
42 | }
43 |
44 | /**
45 | * Register a plugin `fn`.
46 | *
47 | * ```js
48 | * var snapdragon = new Snapdgragon([options]);
49 | * snapdragon.use(function() {
50 | * console.log(this); //<= snapdragon instance
51 | * console.log(this.parser); //<= parser instance
52 | * console.log(this.compiler); //<= compiler instance
53 | * });
54 | * ```
55 | * @param {Object} `fn`
56 | * @api public
57 | */
58 |
59 | Snapdragon.prototype.use = function(fn) {
60 | fn.call(this, this);
61 | return this;
62 | };
63 |
64 | /**
65 | * Define a non-enumerable property or method on the Snapdragon instance.
66 | * Useful in plugins for adding convenience methods that can be used in
67 | * nodes.
68 | *
69 | * ```js
70 | * snapdraong.define('isTypeFoo', function(node) {
71 | * return node.type === 'foo';
72 | * });
73 | *
74 | * // inside a handler
75 | * snapdragon.set('razzle-dazzle', function(node) {
76 | * if (this.isTypeFoo(node.parent)) {
77 | * // do stuff
78 | * }
79 | * });
80 | * ```
81 | * @param {String} `name` Name of the property or method being defined
82 | * @param {any} `val` Property value
83 | * @return {Object} Returns the instance for chaining.
84 | * @api public
85 | */
86 |
87 | Snapdragon.prototype.define = function(key, val) {
88 | define(this, key, val);
89 | return this;
90 | };
91 |
92 | /**
93 | * Parse the given `str` and return an AST.
94 | *
95 | * ```js
96 | * var snapdragon = new Snapdgragon([options]);
97 | * var ast = snapdragon.parse('foo/bar');
98 | * console.log(ast);
99 | * ```
100 | * @param {String} `str`
101 | * @param {Object} `options` Set `options.sourcemap` to true to enable source maps.
102 | * @return {Object} Returns an AST.
103 | * @api public
104 | */
105 |
106 | Snapdragon.prototype.parse = function(str, options) {
107 | var opts = extend({}, this.options, options);
108 | var ast = this.parser.parse(str, opts);
109 | // add non-enumerable parser reference to AST
110 | define(ast, 'parser', this.parser);
111 | return ast;
112 | };
113 |
114 | /**
115 | * Compile an `ast` returned from `snapdragon.parse()`
116 | *
117 | * ```js
118 | * // compile
119 | * var res = snapdragon.compile(ast);
120 | * // get the compiled output string
121 | * console.log(res.output);
122 | * ```
123 | * @param {Object} `ast`
124 | * @param {Object} `options`
125 | * @return {Object} Returns an object with an `output` property with the rendered string.
126 | * @api public
127 | */
128 |
129 | Snapdragon.prototype.compile = function(ast, options) {
130 | var opts = extend({}, this.options, options);
131 | return this.compiler.compile(ast, opts);
132 | };
133 |
134 | /**
135 | * Renders the given string or AST by calling `snapdragon.parse()` (if it's a string)
136 | * then `snapdragon.compile()`, and returns the output string.
137 | *
138 | * ```js
139 | * // setup parsers and compilers, then call render
140 | * var str = snapdragon.render([string_or_ast]);
141 | * console.log(str);
142 | * ```
143 | * @param {Object} `ast`
144 | * @param {Object} `options`
145 | * @return {Object} Returns an object with an `output` property with the rendered string.
146 | * @api public
147 | */
148 |
149 | Snapdragon.prototype.render = function(ast, options) {
150 | if (typeof ast === 'string') {
151 | ast = this.parse(ast, options);
152 | }
153 | return this.compile(ast, options).output;
154 | };
155 |
156 | /**
157 | * Get or set a `Snapdragon.Compiler` instance.
158 | * @api public
159 | */
160 |
161 | Object.defineProperty(Snapdragon.prototype, 'compiler', {
162 | configurable: true,
163 | set: function(val) {
164 | this.cache.compiler = val;
165 | },
166 | get: function() {
167 | if (!this.cache.compiler) {
168 | this.cache.compiler = new Compiler(this.options);
169 | }
170 | return this.cache.compiler;
171 | }
172 | });
173 |
174 | /**
175 | * Get or set a `Snapdragon.Parser` instance.
176 | * @api public
177 | */
178 |
179 | Object.defineProperty(Snapdragon.prototype, 'parser', {
180 | configurable: true,
181 | set: function(val) {
182 | this.cache.parser = val;
183 | },
184 | get: function() {
185 | if (!this.cache.parser) {
186 | this.cache.parser = new Parser(this.options);
187 | }
188 | return this.cache.parser;
189 | }
190 | });
191 |
192 | /**
193 | * Get the compilers from a `Snapdragon.Compiler` instance.
194 | * @api public
195 | */
196 |
197 | Object.defineProperty(Snapdragon.prototype, 'compilers', {
198 | get: function() {
199 | return this.compiler.compilers;
200 | }
201 | });
202 |
203 | /**
204 | * Get the parsers from a `Snapdragon.Parser` instance.
205 | * @api public
206 | */
207 |
208 | Object.defineProperty(Snapdragon.prototype, 'parsers', {
209 | get: function() {
210 | return this.parser.parsers;
211 | }
212 | });
213 |
214 | /**
215 | * Get the regex cache from a `Snapdragon.Parser` instance.
216 | * @api public
217 | */
218 |
219 | Object.defineProperty(Snapdragon.prototype, 'regex', {
220 | get: function() {
221 | return this.parser.regex;
222 | }
223 | });
224 |
225 | /**
226 | * Expose `Parser` and `Compiler`
227 | */
228 |
229 | Snapdragon.Compiler = Compiler;
230 | Snapdragon.Parser = Parser;
231 |
232 | /**
233 | * Expose `Snapdragon`
234 | */
235 |
236 | module.exports = Snapdragon;
237 |
--------------------------------------------------------------------------------
/lib/compiler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var use = require('use');
4 | var util = require('snapdragon-util');
5 | var Emitter = require('component-emitter');
6 | var define = require('define-property');
7 | var extend = require('extend-shallow');
8 | var error = require('./error');
9 |
10 | /**
11 | * Create a new `Compiler` with the given `options`.
12 | *
13 | * ```js
14 | * var Snapdragon = require('snapdragon');
15 | * var Compiler = Snapdragon.Compiler;
16 | * var compiler = new Compiler();
17 | * ```
18 | * @param {Object} `options`
19 | * @param {Object} `state` Optionally pass a "state" object to use inside visitor functions.
20 | * @api public
21 | */
22 |
23 | function Compiler(options, state) {
24 | this.options = extend({source: 'string'}, options);
25 | this.emitter = new Emitter();
26 | this.on = this.emitter.on.bind(this.emitter);
27 | this.isCompiler = true;
28 | this.state = state || {};
29 | this.state.inside = this.state.inside || {};
30 | this.compilers = {};
31 | this.output = '';
32 | this.indent = '';
33 | this.set('eos', function(node) {
34 | return this.emit(node.val, node);
35 | });
36 | this.set('bos', function(node) {
37 | return this.emit(node.val, node);
38 | });
39 | use(this);
40 | }
41 |
42 | /**
43 | * Prototype methods
44 | */
45 |
46 | Compiler.prototype = {
47 |
48 | /**
49 | * Throw a formatted error message with details including the cursor position.
50 | *
51 | * ```js
52 | * compiler.set('foo', function(node) {
53 | * if (node.val !== 'foo') {
54 | * throw this.error('expected node.val to be "foo"', node);
55 | * }
56 | * });
57 | * ```
58 | * @name .error
59 | * @param {String} `msg` Message to use in the Error.
60 | * @param {Object} `node`
61 | * @return {undefined}
62 | * @api public
63 | */
64 |
65 | error: function(/*msg, node*/) {
66 | return error.apply(this, arguments);
67 | },
68 |
69 | /**
70 | * Concat the given string to `compiler.output`.
71 | *
72 | * ```js
73 | * compiler.set('foo', function(node) {
74 | * this.emit(node.val, node);
75 | * });
76 | * ```
77 | * @name .emit
78 | * @param {String} `string`
79 | * @param {Object} `node` Optionally pass the node to use for position if source maps are enabled.
80 | * @return {String} returns the string
81 | * @api public
82 | */
83 |
84 | emit: function(val, node) {
85 | this.output += val;
86 | return val;
87 | },
88 |
89 | /**
90 | * Emit an empty string to effectively "skip" the string for the given `node`,
91 | * but still emit the position and node type.
92 | *
93 | * ```js
94 | * // example: do nothing for beginning-of-string
95 | * snapdragon.compiler.set('bos', compiler.noop);
96 | * ```
97 | * @name .noop
98 | * @param {Object} node
99 | * @api public
100 | */
101 |
102 | noop: function(node) {
103 | this.emit('', node);
104 | },
105 |
106 | /**
107 | * Define a non-enumberable property on the `Compiler` instance. This is useful
108 | * in plugins, for exposing methods inside handlers.
109 | *
110 | * ```js
111 | * compiler.define('customMethod', function() {
112 | * // do stuff
113 | * });
114 | * ```
115 | * @name .define
116 | * @param {String} `key` propery name
117 | * @param {any} `val` property value
118 | * @return {Object} Returns the Compiler instance for chaining.
119 | * @api public
120 | */
121 |
122 | define: function(key, val) {
123 | define(this, key, val);
124 | return this;
125 | },
126 |
127 | /**
128 | * Add a compiler `fn` for the given `type`. Compilers are called
129 | * when the `.compile` method encounters a node of the given type to
130 | * generate the output string.
131 | *
132 | * ```js
133 | * compiler
134 | * .set('comma', function(node) {
135 | * this.emit(',');
136 | * })
137 | * .set('dot', function(node) {
138 | * this.emit('.');
139 | * })
140 | * .set('slash', function(node) {
141 | * this.emit('/');
142 | * });
143 | * ```
144 | * @name .set
145 | * @param {String} `type`
146 | * @param {Function} `fn`
147 | * @api public
148 | */
149 |
150 | set: function(type, fn) {
151 | this.compilers[type] = fn;
152 | return this;
153 | },
154 |
155 | /**
156 | * Get the compiler of the given `type`.
157 | *
158 | * ```js
159 | * var fn = compiler.get('slash');
160 | * ```
161 | * @name .get
162 | * @param {String} `type`
163 | * @api public
164 | */
165 |
166 | get: function(type) {
167 | return this.compilers[type];
168 | },
169 |
170 | /**
171 | * Visit `node` using the registered compiler function associated with the
172 | * `node.type`.
173 | *
174 | * ```js
175 | * compiler
176 | * .set('i', function(node) {
177 | * this.visit(node);
178 | * })
179 | * ```
180 | * @name .visit
181 | * @param {Object} `node`
182 | * @return {Object} returns the node
183 | * @api public
184 | */
185 |
186 | visit: function(node) {
187 | if (util.isOpen(node)) {
188 | util.addType(this.state, node);
189 | }
190 |
191 | this.emitter.emit('node', node);
192 |
193 | var fn = this.compilers[node.type] || this.compilers.unknown;
194 | if (typeof fn !== 'function') {
195 | throw this.error('compiler "' + node.type + '" is not registered', node);
196 | }
197 |
198 | var val = fn.call(this, node) || node;
199 | if (util.isNode(val)) {
200 | node = val;
201 | }
202 |
203 | if (util.isClose(node)) {
204 | util.removeType(this.state, node);
205 | }
206 | return node;
207 | },
208 |
209 | /**
210 | * Iterate over `node.nodes`, calling [visit](#visit) on each node.
211 | *
212 | * ```js
213 | * compiler
214 | * .set('i', function(node) {
215 | * utils.mapVisit(node);
216 | * })
217 | * ```
218 | * @name .mapVisit
219 | * @param {Object} `node`
220 | * @return {Object} returns the node
221 | * @api public
222 | */
223 |
224 | mapVisit: function(parent) {
225 | var nodes = parent.nodes || parent.children;
226 | for (var i = 0; i < nodes.length; i++) {
227 | var node = nodes[i];
228 | if (!node.parent) node.parent = parent;
229 | nodes[i] = this.visit(node) || node;
230 | }
231 | },
232 |
233 | /**
234 | * Compile the given `AST` and return a string. Iterates over `ast.nodes`
235 | * with [mapVisit](#mapVisit).
236 | *
237 | * ```js
238 | * var ast = parser.parse('foo');
239 | * var str = compiler.compile(ast);
240 | * ```
241 | * @name .compile
242 | * @param {Object} `ast`
243 | * @param {Object} `options` Compiler options
244 | * @return {Object} returns the node
245 | * @api public
246 | */
247 |
248 | compile: function(ast, options) {
249 | var opts = extend({}, this.options, options);
250 | this.ast = ast;
251 | this.output = '';
252 |
253 | // source map support
254 | if (opts.sourcemap) {
255 | var sourcemaps = require('./source-maps');
256 | sourcemaps(this);
257 | this.mapVisit(this.ast);
258 | this.applySourceMaps();
259 | this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON();
260 | } else {
261 | this.mapVisit(this.ast);
262 | }
263 |
264 | return this;
265 | }
266 | };
267 |
268 | /**
269 | * Expose `Compiler`
270 | */
271 |
272 | module.exports = Compiler;
273 |
--------------------------------------------------------------------------------
/lib/error.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var get = require('get-value');
4 |
5 | module.exports = function(msg, node) {
6 | node = node || {};
7 | var pos = node.position || {};
8 | var line = get(node, 'position.end.line') || 1;
9 | var column = get(node, 'position.end.column') || 1;
10 | var source = this.options.source;
11 |
12 | var message = source + ' : ' + msg;
13 | var err = new Error(message);
14 | err.source = source;
15 | err.reason = msg;
16 | err.pos = pos;
17 |
18 | if (this.options.silent) {
19 | this.errors.push(err);
20 | } else {
21 | throw err;
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/lib/parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var use = require('use');
4 | var util = require('snapdragon-util');
5 | var Cache = require('map-cache');
6 | var Node = require('snapdragon-node');
7 | var define = require('define-property');
8 | var extend = require('extend-shallow');
9 | var Emitter = require('component-emitter');
10 | var isObject = require('isobject');
11 | var Position = require('./position');
12 | var error = require('./error');
13 |
14 | /**
15 | * Create a new `Parser` with the given `input` and `options`.
16 | *
17 | * ```js
18 | * var Snapdragon = require('snapdragon');
19 | * var Parser = Snapdragon.Parser;
20 | * var parser = new Parser();
21 | * ```
22 | * @param {String} `input`
23 | * @param {Object} `options`
24 | * @api public
25 | */
26 |
27 | function Parser(options) {
28 | this.options = extend({source: 'string'}, options);
29 | this.isParser = true;
30 | this.Node = Node;
31 | this.init(this.options);
32 | use(this);
33 | }
34 |
35 | /**
36 | * Prototype methods
37 | */
38 |
39 | Parser.prototype = Emitter({
40 | constructor: Parser,
41 |
42 | init: function(options) {
43 | this.orig = '';
44 | this.input = '';
45 | this.parsed = '';
46 |
47 | this.currentType = 'root';
48 | this.setCount = 0;
49 | this.count = 0;
50 | this.column = 1;
51 | this.line = 1;
52 |
53 | this.regex = new Cache();
54 | this.errors = this.errors || [];
55 | this.parsers = this.parsers || {};
56 | this.types = this.types || [];
57 | this.sets = this.sets || {};
58 | this.fns = this.fns || [];
59 | this.tokens = [];
60 | this.stack = [];
61 |
62 | this.typeStack = [];
63 | this.setStack = [];
64 |
65 | var pos = this.position();
66 | this.bos = pos(this.node({
67 | type: 'bos',
68 | val: ''
69 | }));
70 |
71 | this.ast = pos(this.node({
72 | type: this.options.astType || 'root',
73 | errors: this.errors
74 | }));
75 |
76 | this.ast.pushNode(this.bos);
77 | this.nodes = [this.ast];
78 | },
79 |
80 | /**
81 | * Throw a formatted error message with details including the cursor position.
82 | *
83 | * ```js
84 | * parser.set('foo', function(node) {
85 | * if (node.val !== 'foo') {
86 | * throw this.error('expected node.val to be "foo"', node);
87 | * }
88 | * });
89 | * ```
90 | * @name .error
91 | * @param {String} `msg` Message to use in the Error.
92 | * @param {Object} `node`
93 | * @return {undefined}
94 | * @api public
95 | */
96 |
97 | error: function(/*msg, node*/) {
98 | return error.apply(this, arguments);
99 | },
100 |
101 | /**
102 | * Define a non-enumberable property on the `Parser` instance. This is useful
103 | * in plugins, for exposing methods inside handlers.
104 | *
105 | * ```js
106 | * parser.define('foo', 'bar');
107 | * ```
108 | * @name .define
109 | * @param {String} `key` propery name
110 | * @param {any} `val` property value
111 | * @return {Object} Returns the Parser instance for chaining.
112 | * @api public
113 | */
114 |
115 | define: function(key, val) {
116 | define(this, key, val);
117 | return this;
118 | },
119 |
120 | /**
121 | * Create a new [Node](#node) with the given `val` and `type`.
122 | *
123 | * ```js
124 | * parser.node('/', 'slash');
125 | * ```
126 | * @name .node
127 | * @param {Object} `val`
128 | * @param {String} `type`
129 | * @return {Object} returns the [Node](#node) instance.
130 | * @api public
131 | */
132 |
133 | node: function(val, type) {
134 | return new this.Node(val, type);
135 | },
136 |
137 | /**
138 | * Mark position and patch `node.position`.
139 | *
140 | * ```js
141 | * parser.set('foo', function(node) {
142 | * var pos = this.position();
143 | * var match = this.match(/foo/);
144 | * if (match) {
145 | * // call `pos` with the node
146 | * return pos(this.node(match[0]));
147 | * }
148 | * });
149 | * ```
150 | * @name .position
151 | * @return {Function} Returns a function that takes a `node`
152 | * @api public
153 | */
154 |
155 | position: function() {
156 | var start = { line: this.line, column: this.column };
157 | var parsed = this.parsed;
158 | var self = this;
159 |
160 | return function(node) {
161 | if (!node.isNode) node = new Node(node);
162 | node.define('position', new Position(start, self));
163 | node.define('parsed', parsed);
164 | node.define('inside', self.stack.length > 0);
165 | node.define('rest', self.input);
166 | return node;
167 | };
168 | },
169 |
170 | /**
171 | * Add parser `type` with the given visitor `fn`.
172 | *
173 | * ```js
174 | * parser.set('all', function() {
175 | * var match = this.match(/^./);
176 | * if (match) {
177 | * return this.node(match[0]);
178 | * }
179 | * });
180 | * ```
181 | * @name .set
182 | * @param {String} `type`
183 | * @param {Function} `fn`
184 | * @api public
185 | */
186 |
187 | set: function(type, fn) {
188 | if (this.types.indexOf(type) === -1) {
189 | this.types.push(type);
190 | }
191 | this.parsers[type] = fn.bind(this);
192 | return this;
193 | },
194 |
195 | /**
196 | * Get parser `type`.
197 | *
198 | * ```js
199 | * var fn = parser.get('slash');
200 | * ```
201 | * @name .get
202 | * @param {String} `type`
203 | * @api public
204 | */
205 |
206 | get: function(type) {
207 | return this.parsers[type];
208 | },
209 |
210 | /**
211 | * Push a node onto the stack for the given `type`.
212 | *
213 | * ```js
214 | * parser.set('all', function() {
215 | * var match = this.match(/^./);
216 | * if (match) {
217 | * var node = this.node(match[0]);
218 | * this.push(node);
219 | * return node;
220 | * }
221 | * });
222 | * ```
223 | * @name .push
224 | * @param {String} `type`
225 | * @return {Object} `token`
226 | * @api public
227 | */
228 |
229 | push: function(type, token) {
230 | this.sets[type] = this.sets[type] || [];
231 | this.count++;
232 | this.stack.push(token);
233 | this.setStack.push(token);
234 | this.typeStack.push(type);
235 | return this.sets[type].push(token);
236 | },
237 |
238 | /**
239 | * Pop a token off of the stack of the given `type`.
240 | *
241 | * ```js
242 | * parser.set('close', function() {
243 | * var match = this.match(/^\}/);
244 | * if (match) {
245 | * var node = this.node({
246 | * type: 'close',
247 | * val: match[0]
248 | * });
249 | *
250 | * this.pop(node.type);
251 | * return node;
252 | * }
253 | * });
254 | * ```
255 | * @name .pop
256 | * @param {String} `type`
257 | * @returns {Object} Returns a token
258 | * @api public
259 | */
260 |
261 | pop: function(type) {
262 | if (this.sets[type]) {
263 | this.count--;
264 | this.stack.pop();
265 | this.setStack.pop();
266 | this.typeStack.pop();
267 | return this.sets[type].pop();
268 | }
269 | },
270 |
271 | /**
272 | * Return true if inside a "set" of the given `type`. Sets are created
273 | * manually by adding a type to `parser.sets`. A node is "inside" a set
274 | * when an `*.open` node for the given `type` was previously pushed onto the set.
275 | * The type is removed from the set by popping it off when the `*.close`
276 | * node for the given type is reached.
277 | *
278 | * ```js
279 | * parser.set('close', function() {
280 | * var pos = this.position();
281 | * var m = this.match(/^\}/);
282 | * if (!m) return;
283 | * if (!this.isInside('bracket')) {
284 | * throw new Error('missing opening bracket');
285 | * }
286 | * });
287 | * ```
288 | * @name .isInside
289 | * @param {String} `type`
290 | * @return {Boolean}
291 | * @api public
292 | */
293 |
294 | isInside: function(type) {
295 | if (typeof type === 'undefined') {
296 | return this.count > 0;
297 | }
298 | if (!Array.isArray(this.sets[type])) {
299 | return false;
300 | }
301 | return this.sets[type].length > 0;
302 | },
303 |
304 | isDirectlyInside: function(type) {
305 | if (typeof type === 'undefined') {
306 | return this.count > 0 ? util.last(this.typeStack) : null;
307 | }
308 | return util.last(this.typeStack) === type;
309 | },
310 |
311 | /**
312 | * Return true if `node` is the given `type`.
313 | *
314 | * ```js
315 | * parser.isType(node, 'brace');
316 | * ```
317 | * @name .isType
318 | * @param {Object} `node`
319 | * @param {String} `type`
320 | * @return {Boolean}
321 | * @api public
322 | */
323 |
324 | isType: function(node, type) {
325 | return node && node.type === type;
326 | },
327 |
328 | /**
329 | * Get the previous AST node from the `parser.stack` (when inside a nested
330 | * context) or `parser.nodes`.
331 | *
332 | * ```js
333 | * var prev = this.prev();
334 | * ```
335 | * @name .prev
336 | * @return {Object}
337 | * @api public
338 | */
339 |
340 | prev: function(n) {
341 | return this.stack.length > 0
342 | ? util.last(this.stack, n)
343 | : util.last(this.nodes, n);
344 | },
345 |
346 | /**
347 | * Update line and column based on `str`.
348 | */
349 |
350 | consume: function(len) {
351 | this.input = this.input.substr(len);
352 | },
353 |
354 | /**
355 | * Returns the string up to the given `substring`,
356 | * if it exists, and advances the cursor position past the substring.
357 | */
358 |
359 | advanceTo: function(str, i) {
360 | var idx = this.input.indexOf(str, i);
361 | if (idx !== -1) {
362 | var val = this.input.slice(0, idx);
363 | this.consume(idx + str.length);
364 | return val;
365 | }
366 | },
367 |
368 | /**
369 | * Update column based on `str`.
370 | */
371 |
372 | updatePosition: function(str, len) {
373 | var lines = str.match(/\n/g);
374 | if (lines) this.line += lines.length;
375 | var i = str.lastIndexOf('\n');
376 | this.column = ~i ? len - i : this.column + len;
377 | this.parsed += str;
378 | this.consume(len);
379 | },
380 |
381 | /**
382 | * Match `regex`, return captures, and update the cursor position by `match[0]` length.
383 | *
384 | * ```js
385 | * // make sure to use the starting regex boundary: "^"
386 | * var match = this.match(/^\./);
387 | * ```
388 | * @name .prev
389 | * @param {RegExp} `regex`
390 | * @return {Object}
391 | * @api public
392 | */
393 |
394 | match: function(regex) {
395 | var m = regex.exec(this.input);
396 | if (m) {
397 | this.updatePosition(m[0], m[0].length);
398 | return m;
399 | }
400 | },
401 |
402 | /**
403 | * Push `node` to `parent.nodes` and assign `node.parent`
404 | */
405 |
406 | pushNode: function(node, parent) {
407 | if (node && parent) {
408 | if (parent === node) parent = this.ast;
409 | define(node, 'parent', parent);
410 |
411 | if (parent.nodes) parent.nodes.push(node);
412 | if (this.sets.hasOwnProperty(parent.type)) {
413 | this.currentType = parent.type;
414 | }
415 | }
416 | },
417 |
418 | /**
419 | * Capture end-of-string
420 | */
421 |
422 | eos: function() {
423 | if (this.input) return;
424 | var pos = this.position();
425 | var prev = this.prev();
426 |
427 | while (prev.type !== 'root' && !prev.visited) {
428 | if (this.options.strict === true) {
429 | throw new SyntaxError('invalid syntax:' + prev);
430 | }
431 |
432 | if (!util.hasOpenAndClose(prev)) {
433 | define(prev.parent, 'escaped', true);
434 | define(prev, 'escaped', true);
435 | }
436 |
437 | this.visit(prev, function(node) {
438 | if (!util.hasOpenAndClose(node.parent)) {
439 | define(node.parent, 'escaped', true);
440 | define(node, 'escaped', true);
441 | }
442 | });
443 |
444 | prev = prev.parent;
445 | }
446 |
447 | var node = pos(this.node(this.append || '', 'eos'));
448 | if (typeof this.options.eos === 'function') {
449 | node = this.options.eos.call(this, node);
450 | }
451 |
452 | if (this.parsers.eos) {
453 | this.parsers.eos.call(this, node);
454 | }
455 |
456 | define(node, 'parent', this.ast);
457 | return node;
458 | },
459 |
460 | /**
461 | * Run parsers to advance the cursor position
462 | */
463 |
464 | getNext: function() {
465 | var parsed = this.parsed;
466 | var len = this.types.length;
467 | var idx = -1;
468 |
469 | while (++idx < len) {
470 | var type = this.types[idx];
471 | var tok = this.parsers[type].call(this);
472 | if (tok === true) {
473 | break;
474 | }
475 |
476 | if (tok) {
477 | tok.type = tok.type || type;
478 | define(tok, 'rest', this.input);
479 | define(tok, 'parsed', parsed);
480 | this.last = tok;
481 | this.tokens.push(tok);
482 | this.emit('node', tok);
483 | return tok;
484 | }
485 | }
486 | },
487 |
488 | /**
489 | * Run parsers to get the next AST node
490 | */
491 |
492 | advance: function() {
493 | var input = this.input;
494 | this.pushNode(this.getNext(), this.prev());
495 |
496 | // if we're here and input wasn't modified, throw an error
497 | if (this.input && input === this.input) {
498 | var chokedOn = this.input.slice(0, 10);
499 | var err = this.error('no parser for: "' + chokedOn, this.last);
500 | if (this.hasListeners('error')) {
501 | this.emit('error', err);
502 | } else {
503 | throw err;
504 | }
505 | }
506 | },
507 |
508 | /**
509 | * Parse the given string an return an AST object.
510 | *
511 | * ```js
512 | * var ast = parser.parse('foo/bar');
513 | * ```
514 | * @param {String} `input`
515 | * @return {Object} Returns an AST with `ast.nodes`
516 | * @api public
517 | */
518 |
519 | parse: function(input) {
520 | if (typeof input !== 'string') {
521 | throw new TypeError('expected a string');
522 | }
523 |
524 | this.init(this.options);
525 | this.orig = input;
526 | this.input = input;
527 |
528 | // run parsers
529 | while (this.input) this.advance();
530 |
531 | // balance unmatched sets, if not disabled
532 | balanceSets(this, this.stack.pop());
533 |
534 | // create end-of-string node
535 | var eos = this.eos();
536 | var ast = this.prev();
537 | if (ast.type === 'root') {
538 | this.pushNode(eos, ast);
539 | }
540 | return this.ast;
541 | },
542 |
543 | /**
544 | * Visit `node` with the given `fn`
545 | */
546 |
547 | visit: function(node, fn) {
548 | if (!isObject(node) || node.isNode !== true) {
549 | throw new Error('expected node to be an instance of Node');
550 | }
551 | if (node.visited) return;
552 | node.define('visited', true);
553 | node = fn(node) || node;
554 | if (node.nodes) {
555 | this.mapVisit(node.nodes, fn, node);
556 | }
557 | return node;
558 | },
559 |
560 | /**
561 | * Map visit over array of `nodes`.
562 | */
563 |
564 | mapVisit: function(nodes, fn, parent) {
565 | for (var i = 0; i < nodes.length; i++) {
566 | this.visit(nodes[i], fn);
567 | }
568 | }
569 | });
570 |
571 | function balanceSets(parser, node) {
572 | if (node && parser.options.strict === true) {
573 | throw parser.error('imbalanced "' + node.type + '": "' + parser.orig + '"');
574 | }
575 | if (node && node.nodes && node.nodes.length) {
576 | var first = node.nodes[0];
577 | first.val = '\\' + first.val;
578 | }
579 | }
580 |
581 | /**
582 | * Expose `Parser`
583 | */
584 |
585 | module.exports = Parser;
586 |
--------------------------------------------------------------------------------
/lib/position.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Store the position for a node
5 | */
6 |
7 | module.exports = function Position(start, parser) {
8 | this.start = start;
9 | this.end = { line: parser.line, column: parser.column };
10 | };
11 |
--------------------------------------------------------------------------------
/lib/source-maps.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var define = require('define-property');
6 | var sourceMapResolve = require('source-map-resolve');
7 | var SourceMap = require('source-map');
8 |
9 | /**
10 | * Expose `mixin()`.
11 | * This code is based on `source-maps-support.js` in reworkcss/css
12 | * https://github.com/reworkcss/css/blob/master/lib/stringify/source-map-support.js
13 | * Copyright (c) 2012 TJ Holowaychuk
14 | */
15 |
16 | module.exports = mixin;
17 |
18 | /**
19 | * Mixin source map support into `compiler`.
20 | *
21 | * @param {Object} `compiler`
22 | * @api public
23 | */
24 |
25 | function mixin(compiler) {
26 | define(compiler, '_comment', compiler.comment);
27 | compiler.map = new SourceMap.SourceMapGenerator();
28 | compiler.position = { line: 1, column: 1 };
29 | compiler.content = {};
30 | compiler.files = {};
31 |
32 | for (var key in exports) {
33 | define(compiler, key, exports[key]);
34 | }
35 | }
36 |
37 | /**
38 | * Update position.
39 | *
40 | * @param {String} str
41 | */
42 |
43 | exports.updatePosition = function(str) {
44 | var lines = str.match(/\n/g);
45 | if (lines) this.position.line += lines.length;
46 | var i = str.lastIndexOf('\n');
47 | this.position.column = ~i ? str.length - i : this.position.column + str.length;
48 | };
49 |
50 | /**
51 | * Emit `str` with `position`.
52 | *
53 | * @param {String} str
54 | * @param {Object} [pos]
55 | * @return {String}
56 | */
57 |
58 | exports.emit = function(str, node) {
59 | var position = node.position || {};
60 | var source = position.source;
61 | if (source) {
62 | if (position.filepath) {
63 | source = unixify(position.filepath);
64 | }
65 |
66 | this.map.addMapping({
67 | source: source,
68 | generated: {
69 | line: this.position.line,
70 | column: Math.max(this.position.column - 1, 0)
71 | },
72 | original: {
73 | line: position.start.line,
74 | column: position.start.column - 1
75 | }
76 | });
77 |
78 | if (position.content) {
79 | this.addContent(source, position);
80 | }
81 | if (position.filepath) {
82 | this.addFile(source, position);
83 | }
84 | }
85 |
86 | this.updatePosition(str);
87 | this.output += str;
88 | return str;
89 | };
90 |
91 | /**
92 | * Adds a file to the source map output if it has not already been added
93 | * @param {String} `file`
94 | * @param {Object} `pos`
95 | */
96 |
97 | exports.addFile = function(file, position) {
98 | if (typeof position.content !== 'string') return;
99 | if (Object.prototype.hasOwnProperty.call(this.files, file)) return;
100 | this.files[file] = position.content;
101 | };
102 |
103 | /**
104 | * Adds a content source to the source map output if it has not already been added
105 | * @param {String} `source`
106 | * @param {Object} `position`
107 | */
108 |
109 | exports.addContent = function(source, position) {
110 | if (typeof position.content !== 'string') return;
111 | if (Object.prototype.hasOwnProperty.call(this.content, source)) return;
112 | this.map.setSourceContent(source, position.content);
113 | };
114 |
115 | /**
116 | * Applies any original source maps to the output and embeds the source file
117 | * contents in the source map.
118 | */
119 |
120 | exports.applySourceMaps = function() {
121 | Object.keys(this.files).forEach(function(file) {
122 | var content = this.files[file];
123 | this.map.setSourceContent(file, content);
124 |
125 | if (this.options.inputSourcemaps === true) {
126 | var originalMap = sourceMapResolve.resolveSync(content, file, fs.readFileSync);
127 | if (originalMap) {
128 | var map = new SourceMap.SourceMapConsumer(originalMap.map);
129 | var relativeTo = originalMap.sourcesRelativeTo;
130 | this.map.applySourceMap(map, file, unixify(path.dirname(relativeTo)));
131 | }
132 | }
133 | }, this);
134 | };
135 |
136 | /**
137 | * Process comments, drops sourceMap comments.
138 | * @param {Object} node
139 | */
140 |
141 | exports.comment = function(node) {
142 | if (/^# sourceMappingURL=/.test(node.comment)) {
143 | return this.emit('', node.position);
144 | }
145 | return this._comment(node);
146 | };
147 |
148 | /**
149 | * Convert backslash in the given string to forward slashes
150 | */
151 |
152 | function unixify(fp) {
153 | return fp.split(/\\+/).join('/');
154 | }
155 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "snapdragon",
3 | "description": "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.",
4 | "version": "0.12.1",
5 | "homepage": "https://github.com/here-be/snapdragon",
6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)",
7 | "contributors": [
8 | "Brian Woodward (https://twitter.com/doowb)",
9 | "Daniel Tschinder (https://github.com/danez)",
10 | "Jon Schlinkert (http://twitter.com/jonschlinkert)"
11 | ],
12 | "repository": "here-be/snapdragon",
13 | "bugs": {
14 | "url": "https://github.com/here-be/snapdragon/issues"
15 | },
16 | "license": "MIT",
17 | "files": [
18 | "index.js",
19 | "lib"
20 | ],
21 | "main": "index.js",
22 | "engines": {
23 | "node": ">=0.10.0"
24 | },
25 | "scripts": {
26 | "test": "mocha"
27 | },
28 | "dependencies": {
29 | "component-emitter": "^1.2.1",
30 | "define-property": "^2.0.2",
31 | "extend-shallow": "^3.0.2",
32 | "get-value": "^2.0.6",
33 | "isobject": "^3.0.0",
34 | "map-cache": "^0.2.2",
35 | "snapdragon-node": "^1.0.6",
36 | "snapdragon-util": "^4.0.0",
37 | "source-map": "^0.5.6",
38 | "source-map-resolve": "^0.6.0",
39 | "use": "^3.1.0"
40 | },
41 | "devDependencies": {
42 | "mocha": "^3.2.0",
43 | "snapdragon-capture-set": "^1.0.1",
44 | "snapdragon-capture": "^0.2.0",
45 | "gulp": "^3.9.1",
46 | "gulp-istanbul": "^1.1.1",
47 | "gulp-eslint": "^3.0.1",
48 | "gulp-mocha": "^3.0.1",
49 | "gulp-unused": "^0.2.1",
50 | "gulp-format-md": "^0.1.11",
51 | "verb-generate-readme": "^0.6.0"
52 | },
53 | "keywords": [
54 | "lexer",
55 | "snapdragon"
56 | ],
57 | "verb": {
58 | "toc": "collapsible",
59 | "layout": "default",
60 | "tasks": [
61 | "readme"
62 | ],
63 | "plugins": [
64 | "gulp-format-md"
65 | ],
66 | "related": {
67 | "description": "A few of the libraries that use snapdragon:",
68 | "implementations": [
69 | "braces",
70 | "breakdance",
71 | "expand-brackets",
72 | "extglob",
73 | "micromatch",
74 | "nanomatch"
75 | ],
76 | "list": [
77 | "snapdragon-capture",
78 | "snapdragon-capture-set",
79 | "snapdragon-node",
80 | "snapdragon-util"
81 | ]
82 | },
83 | "reflinks": [
84 | "css",
85 | "pug",
86 | "snapdragon-capture",
87 | "snapdragon-capture-set",
88 | "snapdragon-node"
89 | ],
90 | "lint": {
91 | "reflinks": true
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/support/src/content/compiling.md:
--------------------------------------------------------------------------------
1 | # Compiling with snapdragon
2 |
3 |
4 | Pre-requisites
5 | 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.
6 |
7 | 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!
8 |
9 |
10 |
--------------------------------------------------------------------------------
/support/src/content/core-concepts.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Core concepts
4 |
5 | - [Lexer](#parser)
6 | * Token Stream
7 | * Token
8 | * Scope
9 | - [Parser](#parser)
10 | * [Node](#node)
11 | * Stack
12 | * [AST](#ast)
13 | - [Compiler](#compiler)
14 | * State
15 | - [Renderer](#renderer)
16 | * Contexts
17 | * Context
18 |
19 | ## Lexer
20 |
21 | - [ ] Token
22 | - [ ] Tokens
23 | - [ ] Scope
24 |
25 | ## Parser
26 |
27 | ### AST
28 |
29 | TODO
30 |
31 | ### Node
32 |
33 | #### Properties
34 |
35 | Officially supported properties
36 |
37 | - `type`
38 | - `val`
39 | - `nodes`
40 |
41 | **Related**
42 |
43 | - The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value.
44 | - The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node.
45 |
46 |
47 | ## Compiler
48 |
49 | TODO
50 |
51 | ## Renderer
52 |
53 | TODO
54 |
55 |
56 | [verb][]
57 |
--------------------------------------------------------------------------------
/support/src/content/crash-course.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | ## Crash course
4 |
5 | ### Parser
6 |
7 | 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.
8 |
9 | **Parsing**
10 |
11 | When a middleware returns a node, the parser updates the string position and starts over again with the first middleware.
12 |
13 | **Parser middleware**
14 |
15 | 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.
16 |
17 |
18 | **Node**
19 |
20 | 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.
21 |
22 | The only required property is `node.type`.
23 |
24 | Every node has a `node.type` that
25 |
26 | 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.
27 |
28 |
29 | of a specific `node.type` that semantically describes the capturing substrings
30 | . Matching is typically performed using a regular expression, but any means can be used.
31 |
32 | Upon capturing a substring, the parser-middleware
33 |
34 | - capturing and/or further processing relevant part(s) of the captured substring
35 | - returning a node with information that semantically describes the substring that was captured, along with
36 |
37 | When a parser returns a node, that indicates
38 |
39 | by calling each user-defined middleware (referred to as "parsers") until one returns a node.
40 | Each parser middleware
41 | middleware
42 | a string and calling user-defined "parsers"
43 |
44 | **AST**
45 |
46 | which is an object with "nodes", where each "node" is an object with a `type`
47 |
48 | **Nodes**
49 |
50 | A `node` is an object that is used for storing and describing information about a captured substring.
51 |
52 | Every node in the AST has a `type` property, and either:
53 |
54 | - `val`: a captured substring
55 | - `nodes`: an array of child nodes
56 |
57 | When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will
58 |
59 | In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST.
60 |
61 | **Example**
62 |
63 | The absolute simplest AST for a single-character string might look something like this:
64 |
65 | ```js
66 | var ast = {
67 | type: 'root',
68 | nodes: [
69 | {
70 | type: 'text',
71 | val: 'a'
72 | }
73 | ]
74 | };
75 | ```
76 |
77 | Nodes may have any additional properties, but they must have
78 |
79 | Parsers and compilers have a one-to-one relationship.
80 |
81 | The parser uses middleware for
82 |
83 |
84 | 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.
85 |
86 | ### Compiler
87 |
88 | 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.
89 |
90 | **A compiler for every parser**
91 |
92 | Parsers and compilers have a one-to-one relationship.
93 |
94 | The parser uses middleware for
95 |
96 |
--------------------------------------------------------------------------------
/support/src/content/getting-started.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Getting started
4 |
5 | [What is snapdragon, and who created it?](overview.html)
6 |
7 | ## Table of contents
8 |
9 | - Installing snapdragon
10 | - Basic usage
11 | - Next steps
12 |
13 |
14 | ## Installing snapdragon
15 |
16 | ## Usage documentation
17 |
18 | **Learn how to use snapdragon**
19 |
20 | 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.
21 |
22 | is API-focused
23 | how to the API methods that are
24 |
25 | ## Developer documentation
26 |
27 | **Learn how to create plugins or hack on snapdragon**
28 |
29 | 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,
30 |
--------------------------------------------------------------------------------
/support/src/content/guides/creating-your-first-parser.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Creating your first Snapdragon parser
3 | ---
4 |
5 | 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.
6 |
7 | Let's go!
8 |
9 | ## Prerequisites
10 |
11 | Before we dive in, let's make sure you have snapdragon installed and setup properly.
12 |
13 | ### Install snapdragon
14 |
15 | You can use either [npm](https://npmjs.com) or [yarn](https://yarnpkg.com/) to install snapdragon:
16 |
17 | **Install with NPM**
18 |
19 | ```sh
20 | $ npm install snapdragon
21 | ```
22 |
23 | **Install with yarn**
24 |
25 | ```sh
26 | $ yarn add snapdragon
27 | ```
28 |
29 | ### Setup snapdragon
30 |
31 | Create a file in the current working directory named `parser.js` (or whatever you prefer), and add the following code:
32 |
33 | ```js
34 | // add snapdragon using node's "require()"
35 | var Snapdragon = require('snapdragon');
36 |
37 | // create an instance of Snapdragon. This is the basis for your very own application.
38 | var snapdragon = new Snapdragon();
39 | ```
40 |
41 | With that out of the way, let's get started on our parser!
42 |
43 | ## Parsing strategy
44 |
45 | 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.
46 |
47 | ### Defining success
48 |
49 | 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!
50 |
51 | **The goal**
52 |
53 | The string we're going to parse is: `foo/*.js` (a basic glob pattern).
54 |
55 | _(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)_
56 |
57 | 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".
58 |
59 | Our parser will be considered "successful" once it is able to do these things.
60 |
61 | ### Begin with the end in mind
62 |
63 | **The expected result**
64 |
65 | 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.
66 |
67 | 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.
68 |
69 | ## Learning by doing
70 |
71 | 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.
72 |
73 | 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.
74 |
75 | **For every node type, there is a parser and a compiler**
76 |
77 |
78 |
79 |
80 |
81 | 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.
82 |
83 | **
84 |
85 |
86 |
87 | node `type` Snapdragon uses "parsers" are the middleware that to capture substrings. This is what we're going to create next.
88 |
89 |
90 | 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.
91 |
92 | Update `parser.js` with the following code:
93 |
94 | ```js
95 | var Snapdragon = require('snapdragon');
96 | var snapdragon = new Snapdragon();
97 |
98 | /**
99 | *
100 | */
101 |
102 | var ast = snapdragon.parse('foo/*.js');
103 | console.log(ast);
104 | ```
105 |
106 | Then run the following command:
107 |
108 | ```sh
109 | $ node parser.js
110 | ```
111 |
112 | You should see an error message that looks something like the following:
113 |
114 | ```console
115 | Error: string : no parser for: "foo/*.js
116 | ```
117 |
118 | There are a few important bits of information in this message:
119 |
120 | - `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.
121 | - `no parser for:` tells us that no "parsers" are registered for the substring that follows in the error message.
122 |
123 |
124 | ###
125 |
--------------------------------------------------------------------------------
/support/src/content/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | WIP (draft)
4 |
--------------------------------------------------------------------------------
/support/src/content/overview.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Overview
4 |
5 | Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome.
6 |
7 | ## What is snapdragon?
8 |
9 | At its heart, snapdragon does two things:
10 |
11 | - Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST
12 | - Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string.
13 |
14 | **Plugins**
15 |
16 |
17 |
18 | ## What can snapdragon do?
19 |
20 | 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.
21 |
22 | **In the wild**
23 |
24 | Here's how some real projects are using snapdragon:
25 |
26 | * [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]:
27 | * [micromatch][]: uses snapdragon to create regex from glob patterns
28 | * [extglob][]: uses snapdragon to create regex from glob patterns
29 | * [braces][]: uses snapdragon to create regex for bash-like brace-expansion
30 | * [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions)
31 |
32 | ## About
33 |
34 | Snapdragon was created by, [Jon Schlinkert]({%= author.url %}), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects.
35 |
36 | If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to:
37 |
38 | - follow me on [github]({%= author.twitter %}) for notifications and updates about my github projects
39 | - follow me on [twitter]({%= author.twitter %})
40 | - connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert)
41 |
--------------------------------------------------------------------------------
/support/src/content/parsing.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Parsing with snapdragon
4 |
5 |
6 | Pre-requisites
7 | 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.
8 |
9 | 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!
10 |
11 |
12 |
13 |
14 | Table of contents
15 | - Usage
16 | - Developer
17 | * Parser
18 | * Parsers
19 | * Custom parsers
20 |
21 |
22 | ## API
23 |
24 | ## Parser
25 |
26 | The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string.
27 |
28 | To understand what `Parser` does,
29 |
30 | The snapdragon parser takes a string and creates an by
31 |
32 | 1. looping over the string
33 | 1. invoking registered [parsers](#parsers) to create new AST nodes.
34 |
35 | The following documentation describes this in more detail.
36 |
37 |
38 | checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and:
39 | * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function)
40 | * if _no matches are found_, an error is throw notifying you that the s
41 |
42 |
43 | ## Parsers
44 |
45 | Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string.
46 |
47 | **How parsers work**
48 |
49 | A very basic parser function might look something like this:
50 |
51 | ```js
52 | function() {
53 | var parsed = this.parsed;
54 | var pos = this.position();
55 | var m = this.match(regex);
56 | if (!m || !m[0]) return;
57 |
58 | var prev = this.prev();
59 | var node = pos({
60 | type: type,
61 | val: m[0]
62 | });
63 |
64 | define(node, 'match', m);
65 | define(node, 'inside', this.stack.length > 0);
66 | define(node, 'parent', prev);
67 | define(node, 'parsed', parsed);
68 | define(node, 'rest', this.input);
69 | prev.nodes.push(node);
70 | }
71 | ```
72 |
73 | TODO
74 |
75 |
76 | ## Custom parsers
77 |
78 | TODO
79 |
80 | ## Plugins
81 |
82 | TODO
83 |
84 |
85 | ```js
86 | parser.use(function() {});
87 | ```
88 |
89 |
90 | ```js
91 | snapdragon.parser.use(function() {});
92 | ```
93 |
--------------------------------------------------------------------------------
/support/src/content/plugins.md:
--------------------------------------------------------------------------------
1 | WIP (draft)
2 |
3 | # Snapdragon plugins
4 |
5 | ```js
6 | var snapdragon = new Snapdgragon();
7 | // register plugins
8 | snapdragon.use(function() {});
9 |
10 | // register parser plugins
11 | snapdragon.parser.use(function() {});
12 |
13 | // register compiler plugins
14 | snapdragon.compiler.use(function() {});
15 |
16 | // parse
17 | var ast = snapdragon.parse('foo/bar');
18 | ```
19 |
--------------------------------------------------------------------------------
/test/compile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Compile = require('../lib/compiler');
6 | var Parser = require('../lib/parser');
7 | var compiler;
8 | var parser;
9 |
10 | describe('compiler', function() {
11 | beforeEach(function() {
12 | compiler = new Compile();
13 | compiler
14 | .set('parens.open', function(node) {
15 | return this.emit('(', node);
16 | })
17 | .set('parens.close', function(node) {
18 | return this.emit(')', node);
19 | });
20 | parser = new Parser();
21 | parser
22 | .set('text', function() {
23 | var pos = this.position();
24 | var match = this.match(/^\w+/);
25 | if (match) {
26 | return pos(this.node(match[0]));
27 | }
28 | })
29 | .set('slash', function() {
30 | var pos = this.position();
31 | var match = this.match(/^\//);
32 | if (match) {
33 | return pos(this.node(match[0]))
34 | }
35 | })
36 | .set('parens.open', function() {
37 | var pos = this.position();
38 | var match = this.match(/^\(/);
39 | if (match) {
40 | return pos(this.node(match[0]))
41 | }
42 | })
43 | .set('parens.close', function() {
44 | var pos = this.position();
45 | var match = this.match(/^\)/);
46 | if (match) {
47 | return pos(this.node(match[0]))
48 | }
49 | });
50 | });
51 |
52 | describe('errors', function(cb) {
53 | it('should throw an error when a compiler is missing', function(cb) {
54 | try {
55 | var ast = parser.parse('a/b/c');
56 | compiler.compile(ast);
57 | cb(new Error('expected an error'));
58 | } catch (err) {
59 | assert(err);
60 | assert.equal(err.message, 'string : compiler "text" is not registered');
61 | cb();
62 | }
63 | });
64 | });
65 |
66 | describe('.compile', function() {
67 | beforeEach(function() {
68 | compiler
69 | .set('text', function(node) {
70 | return this.emit(node.val);
71 | })
72 | .set('slash', function(node) {
73 | return this.emit('-');
74 | });
75 | });
76 |
77 | it('should set the result on `output`', function() {
78 | var ast = parser.parse('a/b/c');
79 | var res = compiler.compile(ast);
80 | assert.equal(res.output, 'a-b-c');
81 | });
82 |
83 | it('should compile close without open', function() {
84 | var ast = parser.parse('a)');
85 | var res = compiler.compile(ast);
86 | assert.equal(res.output, 'a)');
87 | });
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/test/compiler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Compiler = require('../lib/compiler');
6 | var compiler;
7 |
8 | describe('compiler', function() {
9 | beforeEach(function() {
10 | compiler = new Compiler();
11 | });
12 |
13 | describe('constructor:', function() {
14 | it('should return an instance of Compiler:', function() {
15 | assert(compiler instanceof Compiler);
16 | });
17 | });
18 |
19 | // ensures that we catch and document API changes
20 | describe('prototype methods:', function() {
21 | var methods = [
22 | 'error',
23 | 'set',
24 | 'emit',
25 | 'visit',
26 | 'mapVisit',
27 | 'compile'
28 | ];
29 |
30 | methods.forEach(function(method) {
31 | it('should expose the `' + method + '` method', function() {
32 | assert.equal(typeof compiler[method], 'function', method);
33 | });
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/test/nodes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var captureSet = require('snapdragon-capture-set');
7 | var Parser = require('../lib/parser');
8 | var parser;
9 | var ast;
10 |
11 | describe('parser', function() {
12 | beforeEach(function() {
13 | parser = new Parser();
14 | parser.use(captureSet());
15 | parser.captureSet('brace', /^\{/, /^\}/);
16 |
17 | parser.set('text', function() {
18 | var pos = this.position();
19 | var match = this.match(/^[^{}]/);
20 | if (match) {
21 | return pos(this.node(match[0]));
22 | }
23 | });
24 |
25 | parser.set('comma', function() {
26 | var pos = this.position();
27 | var match = this.match(/,/);
28 | if (match) {
29 | return pos(this.node(match[0]));
30 | }
31 | });
32 |
33 | ast = parser.parse('a{b,{c,d},e}f');
34 | });
35 |
36 | describe('.isType', function() {
37 | it('should return true if "node" is the given "type"', function() {
38 | assert(ast.isType('root'));
39 | assert(ast.nodes[0].isType('bos'));
40 | });
41 | });
42 |
43 | describe('.hasType', function() {
44 | it('should return true if "node" has the given "type"', function() {
45 | assert(ast.hasType('bos'));
46 | assert(ast.hasType('eos'));
47 | });
48 | });
49 |
50 | describe('.first', function() {
51 | it('should get the first node in node.nodes', function() {
52 | assert(ast.first);
53 | assert(ast.first.isType('bos'));
54 | });
55 | });
56 |
57 | describe('.last', function() {
58 | it('should get the last node in node.nodes', function() {
59 | assert(ast.last);
60 | assert(ast.last.isType('eos'));
61 | });
62 | });
63 |
64 | describe('.next', function() {
65 | it('should get the next node in an array of nodes', function() {
66 |
67 | // console.log(ast)
68 | });
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/test/parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var Parser = require('../lib/parser');
7 | var parser;
8 |
9 | describe('parser', function() {
10 | beforeEach(function() {
11 | parser = new Parser();
12 | });
13 |
14 | describe('errors', function(cb) {
15 | it('should throw an error when invalid args are passed to parse', function(cb) {
16 | var parser = new Parser();
17 | try {
18 | parser.parse();
19 | cb(new Error('expected an error'));
20 | } catch (err) {
21 | assert(err);
22 | assert.equal(err.message, 'expected a string');
23 | cb();
24 | }
25 | });
26 | });
27 |
28 | describe('bos', function() {
29 | it('should set a beginning-of-string node', function() {
30 | var parser = new Parser();
31 | parser.set('all', function() {
32 | var pos = this.position();
33 | var m = this.match(/^.*/);
34 | if (!m) return;
35 | return pos({
36 | type: 'all',
37 | val: m[0]
38 | });
39 | });
40 |
41 | var ast = parser.parse('a/b');
42 | assert.equal(ast.nodes[0].type, 'bos');
43 | });
44 | });
45 |
46 | describe('eos', function() {
47 | it('should set an end-of-string node', function() {
48 | var parser = new Parser();
49 | parser.set('all', function() {
50 | var pos = this.position();
51 | var m = this.match(/^.*/);
52 | if (!m) return;
53 | return pos({
54 | type: 'all',
55 | val: m[0]
56 | });
57 | });
58 |
59 | var ast = parser.parse('a/b');
60 | assert.equal(ast.nodes[ast.nodes.length - 1].type, 'eos');
61 | });
62 | });
63 |
64 | describe('.set():', function() {
65 | it('should register middleware', function() {
66 | parser.set('all', function() {
67 | var pos = this.position();
68 | var m = this.match(/^.*/);
69 | if (!m) return;
70 | return pos({
71 | type: 'all',
72 | val: m[0]
73 | });
74 | });
75 |
76 | parser.parse('a/b');
77 | assert(parser.parsers.hasOwnProperty('all'));
78 | });
79 |
80 | it('should use middleware to parse', function() {
81 | parser.set('all', function() {
82 | var pos = this.position();
83 | var m = this.match(/^.*/);
84 | if (!m) return;
85 | return pos({
86 | type: 'all',
87 | val: m[0]
88 | });
89 | });
90 |
91 | parser.parse('a/b');
92 | assert.equal(parser.parsed, 'a/b');
93 | assert.equal(parser.input, '');
94 | });
95 |
96 | it('should create ast node:', function() {
97 | parser.set('all', function() {
98 | var pos = this.position();
99 | var m = this.match(/^.*/);
100 | if (!m) return;
101 | return pos({
102 | type: 'all',
103 | val: m[0]
104 | });
105 | });
106 |
107 | parser.parse('a/b');
108 | assert.equal(parser.ast.nodes.length, 3);
109 | });
110 |
111 | it('should be chainable:', function() {
112 | parser
113 | .set('text', function() {
114 | var pos = this.position();
115 | var m = this.match(/^\w+/);
116 | if (!m) return;
117 | return pos({
118 | type: 'text',
119 | val: m[0]
120 | });
121 | })
122 | .set('slash', function() {
123 | var pos = this.position();
124 | var m = this.match(/^\//);
125 | if (!m) return;
126 | return pos({
127 | type: 'slash',
128 | val: m[0]
129 | });
130 | });
131 |
132 | parser.parse('a/b');
133 | assert.equal(parser.ast.nodes.length, 5);
134 | });
135 | });
136 | });
137 |
138 | describe('ast', function() {
139 | beforeEach(function() {
140 | parser = new Parser();
141 | parser
142 | .set('text', function() {
143 | var pos = this.position();
144 | var m = this.match(/^\w+/);
145 | if (!m) return;
146 | return pos({
147 | type: 'text',
148 | val: m[0]
149 | });
150 | })
151 | .set('slash', function() {
152 | var pos = this.position();
153 | var m = this.match(/^\//);
154 | if (!m) return;
155 | return pos({
156 | type: 'slash',
157 | val: m[0]
158 | });
159 | });
160 | });
161 |
162 | describe('orig:', function() {
163 | it('should add pattern to orig property', function() {
164 | parser.parse('a/b');
165 | assert.equal(parser.orig, 'a/b');
166 | });
167 | });
168 |
169 | describe('recursion', function() {
170 | beforeEach(function() {
171 | parser.set('text', function() {
172 | var pos = this.position();
173 | var m = this.match(/^\w/);
174 | if (!m) return;
175 | return pos({
176 | val: m[0]
177 | });
178 | });
179 |
180 | parser.set('open', function() {
181 | var pos = this.position();
182 | var m = this.match(/^\{/);
183 | if (!m) return;
184 | return pos({
185 | val: m[0]
186 | });
187 | });
188 |
189 | parser.set('close', function() {
190 | var pos = this.position();
191 | var m = this.match(/^\}/);
192 | if (!m) return;
193 | return pos({
194 | val: m[0]
195 | });
196 | });
197 |
198 | parser.set('comma', function() {
199 | var pos = this.position();
200 | var m = this.match(/,/);
201 | if (!m) return;
202 | return pos({
203 | val: m[0]
204 | });
205 | });
206 | });
207 |
208 | it('should set original string on `orig`', function() {
209 | parser.parse('a{b,{c,d},e}f');
210 | assert.equal(parser.orig, 'a{b,{c,d},e}f');
211 | });
212 | });
213 | });
214 |
--------------------------------------------------------------------------------
/test/parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Parser = require('../lib/parser');
6 | var parser;
7 |
8 | describe('parser', function() {
9 | beforeEach(function() {
10 | parser = new Parser();
11 | });
12 |
13 | describe('constructor:', function() {
14 | it('should return an instance of Parser:', function() {
15 | assert(parser instanceof Parser);
16 | });
17 | });
18 |
19 | // ensures that we catch and document API changes
20 | describe('prototype methods:', function() {
21 | var methods = [
22 | 'updatePosition',
23 | 'position',
24 | 'error',
25 | 'set',
26 | 'parse',
27 | 'match',
28 | 'use'
29 | ];
30 |
31 | methods.forEach(function(method) {
32 | it('should expose the `' + method + '` method', function() {
33 | assert.equal(typeof parser[method], 'function');
34 | });
35 | });
36 | });
37 |
38 | describe('parsers', function() {
39 | beforeEach(function() {
40 | parser = new Parser();
41 | });
42 |
43 | describe('.set():', function() {
44 | it('should register a named middleware', function() {
45 | parser.set('all', function() {
46 | var pos = this.position();
47 | var m = this.match(/^.*/);
48 | if (!m) return;
49 | return pos({
50 | type: 'all',
51 | val: m[0]
52 | });
53 | });
54 |
55 | assert(typeof parser.parsers.all === 'function');
56 | });
57 |
58 | it('should expose named parsers to middleware:', function() {
59 | var count = 0;
60 |
61 | parser.set('word', function() {
62 | var pos = this.position();
63 | var m = this.match(/^\w/);
64 | if (!m) return;
65 |
66 | return pos({
67 | type: 'word',
68 | val: m[0]
69 | });
70 | });
71 |
72 | parser.set('slash', function() {
73 | var pos = this.position();
74 | var m = this.match(/^\//);
75 | if (!m) return;
76 |
77 | var word = this.parsers.word();
78 | var prev = this.prev();
79 |
80 | var node = pos({
81 | type: 'slash',
82 | val: m[0]
83 | });
84 |
85 | if (word && word.type === 'word') {
86 | count++;
87 | }
88 |
89 | prev.nodes.push(node);
90 | prev.nodes.push(word);
91 | });
92 |
93 | parser.parse('a/b');
94 | assert.equal(parser.ast.nodes.length, 5);
95 | assert.equal(count, 1);
96 | });
97 | });
98 | });
99 | });
100 |
--------------------------------------------------------------------------------
/test/position.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Position = require('../lib/position');
6 |
7 | describe('Position', function() {
8 | it('should export a function', function() {
9 | assert.equal(typeof Position, 'function');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/test/snapdragon.capture.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var capture = require('snapdragon-capture');
7 | var snapdragon;
8 |
9 | describe('.capture (plugin usage)', function() {
10 | beforeEach(function() {
11 | snapdragon = new Snapdragon();
12 | snapdragon.use(capture());
13 | });
14 |
15 | describe('errors', function(cb) {
16 | it('should throw an error when invalid args are passed to parse', function(cb) {
17 | try {
18 | snapdragon.parse();
19 | cb(new Error('expected an error'));
20 | } catch (err) {
21 | assert(err);
22 | assert.equal(err.message, 'expected a string');
23 | cb();
24 | }
25 | });
26 | });
27 |
28 | describe('.capture():', function() {
29 | it('should register a parser', function() {
30 | snapdragon.capture('all', /^.*/);
31 | snapdragon.parse('a/b');
32 | assert(snapdragon.parsers.hasOwnProperty('all'));
33 | });
34 |
35 | it('should use middleware to parse', function() {
36 | snapdragon.capture('all', /^.*/);
37 | snapdragon.parse('a/b');
38 | assert.equal(snapdragon.parser.parsed, 'a/b');
39 | assert.equal(snapdragon.parser.input, '');
40 | });
41 |
42 | it('should create ast node:', function() {
43 | snapdragon.capture('all', /^.*/);
44 | snapdragon.parse('a/b');
45 | assert.equal(snapdragon.parser.ast.nodes.length, 3);
46 | });
47 |
48 | it('should be chainable:', function() {
49 | snapdragon.parser
50 | .capture('text', /^\w+/)
51 | .capture('slash', /^\//);
52 |
53 | snapdragon.parse('a/b');
54 | assert.equal(snapdragon.parser.ast.nodes.length, 5);
55 | });
56 | });
57 | });
58 |
59 | describe('ast', function() {
60 | beforeEach(function() {
61 | snapdragon = new Snapdragon();
62 | snapdragon.use(capture());
63 | snapdragon
64 | .capture('text', /^\w+/)
65 | .capture('slash', /^\//);
66 | });
67 |
68 | describe('orig:', function() {
69 | it('should add pattern to orig property', function() {
70 | snapdragon.parse('a/b');
71 | assert.equal(snapdragon.parser.orig, 'a/b');
72 | });
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/test/snapdragon.compile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var snapdragon;
7 | var parser;
8 |
9 | describe('snapdragon.compiler', function() {
10 | beforeEach(function() {
11 | snapdragon = new Snapdragon();
12 | snapdragon.parser
13 | .set('text', function() {
14 | var pos = this.position();
15 | var match = this.match(/^\w+/);
16 | if (match) {
17 | return pos(this.node(match[0]));
18 | }
19 | })
20 | .set('slash', function() {
21 | var pos = this.position();
22 | var match = this.match(/^\//);
23 | if (match) {
24 | return pos(this.node(match[0]))
25 | }
26 | });
27 | });
28 |
29 | describe('errors', function(cb) {
30 | it('should throw an error when a compiler is missing', function(cb) {
31 | try {
32 | var ast = snapdragon.parse('a/b/c');
33 | snapdragon.compile(ast);
34 | cb(new Error('expected an error'));
35 | } catch (err) {
36 | assert(err);
37 | assert.equal(err.message, 'string : compiler "text" is not registered');
38 | cb();
39 | }
40 | });
41 | });
42 |
43 | describe('snapdragon.compile', function() {
44 | beforeEach(function() {
45 | snapdragon.compiler
46 | .set('text', function(node) {
47 | return this.emit(node.val);
48 | })
49 | .set('slash', function(node) {
50 | return this.emit('-');
51 | });
52 | });
53 |
54 | it('should set the result on `output`', function() {
55 | var ast = snapdragon.parse('a/b/c');
56 | var res = snapdragon.compile(ast);
57 | assert.equal(res.output, 'a-b-c');
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/snapdragon.options.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 |
7 | describe('.options', function() {
8 | it('should correctly accept and store options in constructor', function() {
9 | var snap = new Snapdragon({
10 | a: true,
11 | b: null,
12 | c: false,
13 | d: 'd'
14 | });
15 |
16 | assert.strictEqual(snap.options['a'], true);
17 | assert.strictEqual(snap.options['b'], null);
18 | assert.strictEqual(snap.options['c'], false);
19 | assert.strictEqual(snap.options['d'], 'd');
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/snapdragon.parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var snapdragon;
7 |
8 | describe('parser', function() {
9 | beforeEach(function() {
10 | snapdragon = new Snapdragon();
11 | });
12 |
13 | describe('errors', function(cb) {
14 | it('should throw an error when invalid args are passed to parse', function(cb) {
15 | try {
16 | snapdragon.parse();
17 | cb(new Error('expected an error'));
18 | } catch (err) {
19 | assert(err);
20 | assert.equal(err.message, 'expected a string');
21 | cb();
22 | }
23 | });
24 | });
25 |
26 | describe('.set():', function() {
27 | it('should register middleware', function() {
28 | snapdragon.parser.set('all', function() {
29 | var pos = this.position();
30 | var m = this.match(/^.*/);
31 | if (!m) return;
32 | return pos({
33 | type: 'all',
34 | val: m[0]
35 | });
36 | });
37 |
38 | snapdragon.parse('a/b');
39 | assert(snapdragon.parsers.hasOwnProperty('all'));
40 | });
41 |
42 | it('should use middleware to parse', function() {
43 | snapdragon.parser.set('all', function() {
44 | var pos = this.position();
45 | var m = this.match(/^.*/);
46 | return pos({
47 | type: 'all',
48 | val: m[0]
49 | });
50 | });
51 |
52 | snapdragon.parse('a/b');
53 | assert.equal(snapdragon.parser.parsed, 'a/b');
54 | assert.equal(snapdragon.parser.input, '');
55 | });
56 |
57 | it('should create ast node:', function() {
58 | snapdragon.parser.set('all', function() {
59 | var pos = this.position();
60 | var m = this.match(/^.*/);
61 | return pos({
62 | type: 'all',
63 | val: m[0]
64 | });
65 | });
66 |
67 | snapdragon.parse('a/b');
68 | assert.equal(snapdragon.parser.ast.nodes.length, 3);
69 | });
70 |
71 | it('should be chainable:', function() {
72 | snapdragon.parser
73 | .set('text', function() {
74 | var pos = this.position();
75 | var m = this.match(/^\w+/);
76 | if (!m) return;
77 | return pos({
78 | type: 'text',
79 | val: m[0]
80 | });
81 | })
82 | .set('slash', function() {
83 | var pos = this.position();
84 | var m = this.match(/^\//);
85 | if (!m) return;
86 | return pos({
87 | type: 'slash',
88 | val: m[0]
89 | });
90 | });
91 |
92 | snapdragon.parse('a/b');
93 | assert.equal(snapdragon.parser.ast.nodes.length, 5);
94 | });
95 | });
96 | });
97 |
98 | describe('ast', function() {
99 | beforeEach(function() {
100 | snapdragon = new Snapdragon();
101 | snapdragon.parser
102 | .set('text', function() {
103 | var pos = this.position();
104 | var m = this.match(/^\w+/);
105 | if (!m) return;
106 | return pos({
107 | type: 'text',
108 | val: m[0]
109 | });
110 | })
111 | .set('slash', function() {
112 | var pos = this.position();
113 | var m = this.match(/^\//);
114 | if (!m) return;
115 | return pos({
116 | type: 'slash',
117 | val: m[0]
118 | });
119 | });
120 | });
121 |
122 | describe('orig:', function() {
123 | it('should add pattern to orig property', function() {
124 | snapdragon.parse('a/b');
125 | assert.equal(snapdragon.parser.orig, 'a/b');
126 | });
127 | });
128 |
129 | describe('recursion', function() {
130 | beforeEach(function() {
131 | snapdragon.parser.set('text', function() {
132 | var pos = this.position();
133 | var m = this.match(/^\w/);
134 | if (!m) return;
135 | return pos({
136 | type: 'text',
137 | val: m[0]
138 | });
139 | });
140 |
141 | snapdragon.parser.set('open', function() {
142 | var pos = this.position();
143 | var m = this.match(/^{/);
144 | if (!m) return;
145 | return pos({
146 | type: 'open',
147 | val: m[0]
148 | });
149 | });
150 |
151 | snapdragon.parser.set('close', function() {
152 | var pos = this.position();
153 | var m = this.match(/^}/);
154 | if (!m) return;
155 | return pos({
156 | type: 'close',
157 | val: m[0]
158 | });
159 | });
160 |
161 | snapdragon.parser.set('comma', function() {
162 | var pos = this.position();
163 | var m = this.match(/,/);
164 | if (!m) return;
165 | return pos({
166 | type: 'comma',
167 | val: m[0]
168 | });
169 | });
170 | });
171 |
172 | it('should set original string on `orig`', function() {
173 | snapdragon.parse('a{b,{c,d},e}f');
174 | assert.equal(snapdragon.parser.orig, 'a{b,{c,d},e}f');
175 | });
176 | });
177 | });
178 |
--------------------------------------------------------------------------------
/test/snapdragon.regex.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Snapdragon = require('..');
6 | var capture = require('snapdragon-capture');
7 | var snapdragon;
8 |
9 | describe('parser', function() {
10 | beforeEach(function() {
11 | snapdragon = new Snapdragon();
12 | snapdragon.use(capture());
13 | });
14 |
15 | describe('.regex():', function() {
16 | it('should expose a regex cache with regex from registered parsers', function() {
17 | snapdragon.capture('dot', /^\./);
18 | snapdragon.capture('text', /^\w+/);
19 | snapdragon.capture('all', /^.+/);
20 |
21 | assert(snapdragon.regex.__data__.hasOwnProperty('dot'));
22 | assert(snapdragon.regex.__data__.hasOwnProperty('all'));
23 | assert(snapdragon.regex.__data__.hasOwnProperty('text'));
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/verbfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(verb) {
4 | verb.use(require('verb-generate-readme'));
5 | verb.preLayout(/\.md$/, function(file, next) {
6 | if (!/(verb|readme)/.test(file.stem)) {
7 | file.layout = null;
8 | }
9 | next();
10 | });
11 |
12 | verb.task('docs', function(cb) {
13 | return verb.src('support/src/content/*.md', {cwd: __dirname})
14 | .pipe(verb.renderFile('md', {layout: null}))
15 | .pipe(verb.dest('docs'))
16 | });
17 |
18 | verb.task('default', ['docs', 'readme']);
19 | };
20 |
--------------------------------------------------------------------------------