pax_global_header00006660000000000000000000000064140340436040014510gustar00rootroot0000000000000052 comment=fb340c536871669c83601ce6f6cb49d9ff730fac snapdragon-0.12.1/000077500000000000000000000000001403404360400137255ustar00rootroot00000000000000snapdragon-0.12.1/.editorconfig000066400000000000000000000004411403404360400164010ustar00rootroot00000000000000# http://editorconfig.org/ root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [{**/{actual,fixtures,expected,templates}/**,*.md}] trim_trailing_whitespace = false insert_final_newline = false snapdragon-0.12.1/.eslintrc.json000066400000000000000000000073251403404360400165300ustar00rootroot00000000000000{ "extends": [ "eslint:recommended" ], "env": { "browser": false, "es6": true, "node": true, "mocha": true }, "parserOptions":{ "ecmaVersion": 9, "sourceType": "module", "ecmaFeatures": { "modules": true, "experimentalObjectRestSpread": true } }, "globals": { "document": false, "navigator": false, "window": false }, "rules": { "accessor-pairs": 2, "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], "brace-style": [2, "1tbs", { "allowSingleLine": true }], "comma-dangle": [2, "never"], "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, "curly": [2, "multi-line"], "dot-location": [2, "property"], "eol-last": 2, "eqeqeq": [2, "allow-null"], "generator-star-spacing": [2, { "before": true, "after": true }], "handle-callback-err": [2, "^(err|error)$" ], "indent": [2, 2, { "SwitchCase": 1 }], "key-spacing": [2, { "beforeColon": false, "afterColon": true }], "keyword-spacing": [2, { "before": true, "after": true }], "new-cap": [2, { "newIsCap": true, "capIsNew": false }], "new-parens": 2, "no-array-constructor": 2, "no-caller": 2, "no-class-assign": 2, "no-cond-assign": 2, "no-const-assign": 2, "no-control-regex": 2, "no-debugger": 2, "no-delete-var": 2, "no-dupe-args": 2, "no-dupe-class-members": 2, "no-dupe-keys": 2, "no-duplicate-case": 2, "no-empty-character-class": 2, "no-eval": 2, "no-ex-assign": 2, "no-extend-native": 2, "no-extra-bind": 2, "no-extra-boolean-cast": 2, "no-extra-parens": [2, "functions"], "no-fallthrough": 2, "no-floating-decimal": 2, "no-func-assign": 2, "no-implied-eval": 2, "no-inner-declarations": [2, "functions"], "no-invalid-regexp": 2, "no-irregular-whitespace": 2, "no-iterator": 2, "no-label-var": 2, "no-labels": 2, "no-lone-blocks": 2, "no-mixed-spaces-and-tabs": 2, "no-multi-spaces": 2, "no-multi-str": 2, "no-multiple-empty-lines": [2, { "max": 1 }], "no-native-reassign": 0, "no-negated-in-lhs": 2, "no-new": 2, "no-new-func": 2, "no-new-object": 2, "no-new-require": 2, "no-new-wrappers": 2, "no-obj-calls": 2, "no-octal": 2, "no-octal-escape": 2, "no-proto": 0, "no-redeclare": 2, "no-regex-spaces": 2, "no-return-assign": 2, "no-self-compare": 2, "no-sequences": 2, "no-shadow-restricted-names": 2, "no-spaced-func": 2, "no-sparse-arrays": 2, "no-this-before-super": 2, "no-throw-literal": 2, "no-trailing-spaces": 0, "no-undef": 2, "no-undef-init": 2, "no-unexpected-multiline": 2, "no-unneeded-ternary": [2, { "defaultAssignment": false }], "no-unreachable": 2, "no-unused-vars": [2, { "vars": "all", "args": "none" }], "no-useless-call": 0, "no-with": 2, "one-var": [0, { "initialized": "never" }], "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], "padded-blocks": [0, "never"], "quotes": [2, "single", "avoid-escape"], "radix": 2, "semi": [2, "always"], "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [2, "always"], "space-before-function-paren": [2, "never"], "space-in-parens": [2, "never"], "space-infix-ops": 2, "space-unary-ops": [2, { "words": true, "nonwords": false }], "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], "use-isnan": 2, "valid-typeof": 2, "wrap-iife": [2, "any"], "yoda": [2, "never"] } } snapdragon-0.12.1/.gitattributes000066400000000000000000000000171403404360400166160ustar00rootroot00000000000000*.* text eol=lfsnapdragon-0.12.1/.gitignore000066400000000000000000000004561403404360400157220ustar00rootroot00000000000000# always ignore files *.DS_Store .idea .vscode *.sublime-* # test related, or directories generated by tests test/actual actual coverage .nyc* # npm node_modules npm-debug.log # yarn yarn.lock yarn-error.log # misc _gh_pages _draft _drafts bower_components vendor temp tmp TODO.md package-lock.jsonsnapdragon-0.12.1/.travis.yml000066400000000000000000000002471403404360400160410ustar00rootroot00000000000000sudo: false os: - linux - osx language: node_js node_js: - node - '14' - '12' - '10' - '9' - '8' - '7' - '6' - '5' - '4' - '0.12' - '0.10' snapdragon-0.12.1/.verb.md000066400000000000000000000123221403404360400152630ustar00rootroot00000000000000Created by [jonschlinkert]({%= author.url %}) and [doowb](https://github.com/doowb). **Features** - Bootstrap your own parser, get sourcemap support for free - All parsing and compiling is handled by simple, reusable middleware functions - Inspired by the parsers in [pug][] and [css][]. ## Quickstart example All of the examples in this document assume the following two lines of setup code exist first: ```js var Snapdragon = require('{%= name %}'); var snapdragon = new Snapdragon(); ``` **Parse a string** ```js var ast = snapdragon.parser // parser handlers (essentially middleware) // used for parsing substrings to create tokens .set('foo', function () {}) .set('bar', function () {}) .parse('some string', options); ``` **Compile an AST returned from `.parse()`** ```js var result = snapdragon.compiler // compiler handlers (essentially middleware), // called on a node when the `node.type` matches // the name of the handler .set('foo', function () {}) .set('bar', function () {}) // pass the `ast` from the parse method .compile(ast) // the compiled string console.log(result.output); ``` See the [examples](./examples/). ## Parsing **Parser handlers** Parser handlers are middleware functions responsible for matching substrings to create tokens: **Example handler** ```js var ast = snapdragon.parser .set('dot', function() { var pos = this.position(); var m = this.match(/^\./); if (!m) return; return pos({ // the "type" will be used by the compiler later on, // we'll go over this in the compiler docs type: 'dot', // "val" is the string captured by ".match", // in this case that would be '.' val: m[0] }); }) .parse('.'[, options]) ``` _As a side node, it's not scrictly required to set the `type` on the token, since the parser will add it to the token if it's undefined, based on the name of the handler. But it's good practice since tokens aren't always returned._ **Example token** And the resulting tokens look something like this: ```js { type: 'dot', val: '.' } ``` **Position** Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured: ```js { type: 'dot', val: '.', position: { start: { lineno: 1, column: 1 }, end: { lineno: 1, column: 2 } }} ``` **Life as an AST node** When the token is returned, the parser pushes it onto the `nodes` array of the "previous" node (since we're in a tree, the "previous" node might be literally the last node that was created, or it might be the "parent" node inside a nested context, like when parsing brackets or something with an open or close), at which point the token begins its life as an AST node. **Wrapping up** In the parser calls all handlers and cannot find a match for a substring, an error is thrown. Assuming the parser finished parsing the entire string, an AST is returned. ## Compiling The compiler's job is to take the AST created by the [parser](#parsing) and convert it to a new string. It does this by iterating over each node on the AST and calling a function on the node based on its `type`. This function is called a "handler". **Compiler handlers** Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler. ```js var result = snapdragon.compiler .set('dot', function (node) { console.log(node.val) //=> '.' return this.emit(node.val); }) ``` If `node.type` does not match a registered handler, an error is thrown. **Source maps** If you want source map support, make sure to emit the entire node as the second argument as well (this allows the compiler to get the `node.position`). ```js var res = snapdragon.compiler .set('dot', function (node) { return this.emit(node.val, node); }) ``` ## All together This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot. ```js var Snapdragon = require('..'); var snapdragon = new Snapdragon(); var ast = snapdragon.parser .set('dot', function () { var pos = this.position(); var m = this.match(/^\./); if (!m) return; return pos({ type: 'dot', val: m[0] }) }) .parse('.') var result = snapdragon.compiler .set('dot', function (node) { return this.emit('\\' + node.val); }) .compile(ast) console.log(result.output); //=> '\.' ``` ## API ### Parse {%= apidocs("lib/parser.js") %} ### Compile {%= apidocs("lib/compiler.js") %} ## Snapdragon in the wild {%= verb.related.description %} {%= related(verb.related.implementations) %} ## History ### v0.9.0 **Breaking changes!** In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release. - `parser.capture` was externalized to [snapdragon-capture][] - `parser.capturePair` was externalized to [snapdragon-capture-set][] - Nodes are now an instance of [snapdragon-node][] ### v0.5.0 **Breaking changes!** Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class. - Renderer was renamed to `Compiler` - the `.render` method was renamed to `.compile` snapdragon-0.12.1/LICENSE000066400000000000000000000021001403404360400147230ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. snapdragon-0.12.1/README.md000066400000000000000000000462601403404360400152140ustar00rootroot00000000000000# snapdragon [![NPM version](https://img.shields.io/npm/v/snapdragon.svg?style=flat)](https://www.npmjs.com/package/snapdragon) [![NPM monthly downloads](https://img.shields.io/npm/dm/snapdragon.svg?style=flat)](https://npmjs.org/package/snapdragon) [![NPM total downloads](https://img.shields.io/npm/dt/snapdragon.svg?style=flat)](https://npmjs.org/package/snapdragon) [![Linux Build Status](https://img.shields.io/travis/here-be/snapdragon.svg?style=flat&label=Travis)](https://travis-ci.org/here-be/snapdragon) > Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support. Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support. ## Table of Contents
Details - [Install](#install) - [Quickstart example](#quickstart-example) - [Parsing](#parsing) - [Compiling](#compiling) - [All together](#all-together) - [API](#api) * [Parse](#parse) * [Compile](#compile) - [Snapdragon in the wild](#snapdragon-in-the-wild) - [History](#history) * [v0.9.0](#v090) * [v0.5.0](#v050) - [About](#about)
## Install Install with [npm](https://www.npmjs.com/): ```sh $ npm install --save snapdragon ``` Created by [jonschlinkert](https://github.com/jonschlinkert) and [doowb](https://github.com/doowb). **Features** * Bootstrap your own parser, get sourcemap support for free * All parsing and compiling is handled by simple, reusable middleware functions * Inspired by the parsers in [pug](https://pugjs.org/) and [css](https://github.com/reworkcss/css). ## Quickstart example All of the examples in this document assume the following two lines of setup code exist first: ```js var Snapdragon = require('snapdragon'); var snapdragon = new Snapdragon(); ``` **Parse a string** ```js var ast = snapdragon.parser // parser handlers (essentially middleware) // used for parsing substrings to create tokens .set('foo', function () {}) .set('bar', function () {}) .parse('some string', options); ``` **Compile an AST returned from `.parse()`** ```js var result = snapdragon.compiler // compiler handlers (essentially middleware), // called on a node when the `node.type` matches // the name of the handler .set('foo', function () {}) .set('bar', function () {}) // pass the `ast` from the parse method .compile(ast) // the compiled string console.log(result.output); ``` See the [examples](./examples/). ## Parsing **Parser handlers** Parser handlers are middleware functions responsible for matching substrings to create tokens: **Example handler** ```js var ast = snapdragon.parser .set('dot', function() { var pos = this.position(); var m = this.match(/^\./); if (!m) return; return pos({ // the "type" will be used by the compiler later on, // we'll go over this in the compiler docs type: 'dot', // "val" is the string captured by ".match", // in this case that would be '.' val: m[0] }); }) .parse('.'[, options]) ``` _As a side node, it's not scrictly required to set the `type` on the token, since the parser will add it to the token if it's undefined, based on the name of the handler. But it's good practice since tokens aren't always returned._ **Example token** And the resulting tokens look something like this: ```js { type: 'dot', val: '.' } ``` **Position** Next, `pos()` is called on the token as it's returned, which patches the token with the `position` of the string that was captured: ```js { type: 'dot', val: '.', position: { start: { lineno: 1, column: 1 }, end: { lineno: 1, column: 2 } }} ``` **Life as an AST node** When the token is returned, the parser pushes it onto the `nodes` array of the "previous" node (since we're in a tree, the "previous" node might be literally the last node that was created, or it might be the "parent" node inside a nested context, like when parsing brackets or something with an open or close), at which point the token begins its life as an AST node. **Wrapping up** In the parser calls all handlers and cannot find a match for a substring, an error is thrown. Assuming the parser finished parsing the entire string, an AST is returned. ## Compiling The compiler's job is to take the AST created by the [parser](#parsing) and convert it to a new string. It does this by iterating over each node on the AST and calling a function on the node based on its `type`. This function is called a "handler". **Compiler handlers** Handlers are _named_ middleware functions that are called on a node when `node.type` matches the name of a registered handler. ```js var result = snapdragon.compiler .set('dot', function (node) { console.log(node.val) //=> '.' return this.emit(node.val); }) ``` If `node.type` does not match a registered handler, an error is thrown. **Source maps** If you want source map support, make sure to emit the entire node as the second argument as well (this allows the compiler to get the `node.position`). ```js var res = snapdragon.compiler .set('dot', function (node) { return this.emit(node.val, node); }) ``` ## All together This is a very basic example, but it shows how to parse a dot, then compile it as an escaped dot. ```js var Snapdragon = require('..'); var snapdragon = new Snapdragon(); var ast = snapdragon.parser .set('dot', function () { var pos = this.position(); var m = this.match(/^\./); if (!m) return; return pos({ type: 'dot', val: m[0] }) }) .parse('.') var result = snapdragon.compiler .set('dot', function (node) { return this.emit('\\' + node.val); }) .compile(ast) console.log(result.output); //=> '\.' ``` ## API ### [Parser](lib/parser.js#L27) Create a new `Parser` with the given `input` and `options`. **Params** * `input` **{String}** * `options` **{Object}** **Example** ```js var Snapdragon = require('snapdragon'); var Parser = Snapdragon.Parser; var parser = new Parser(); ``` ### [.error](lib/parser.js#L97) Throw a formatted error message with details including the cursor position. **Params** * `msg` **{String}**: Message to use in the Error. * `node` **{Object}** * `returns` **{undefined}** **Example** ```js parser.set('foo', function(node) { if (node.val !== 'foo') { throw this.error('expected node.val to be "foo"', node); } }); ``` ### [.define](lib/parser.js#L115) Define a non-enumberable property on the `Parser` instance. This is useful in plugins, for exposing methods inside handlers. **Params** * `key` **{String}**: propery name * `val` **{any}**: property value * `returns` **{Object}**: Returns the Parser instance for chaining. **Example** ```js parser.define('foo', 'bar'); ``` ### [.node](lib/parser.js#L133) Create a new [Node](#node) with the given `val` and `type`. **Params** * `val` **{Object}** * `type` **{String}** * `returns` **{Object}**: returns the [Node](#node) instance. **Example** ```js parser.node('/', 'slash'); ``` ### [.position](lib/parser.js#L155) Mark position and patch `node.position`. * `returns` **{Function}**: Returns a function that takes a `node` **Example** ```js parser.set('foo', function(node) { var pos = this.position(); var match = this.match(/foo/); if (match) { // call `pos` with the node return pos(this.node(match[0])); } }); ``` ### [.set](lib/parser.js#L187) Add parser `type` with the given visitor `fn`. **Params** * `type` **{String}** * `fn` **{Function}** **Example** ```js parser.set('all', function() { var match = this.match(/^./); if (match) { return this.node(match[0]); } }); ``` ### [.get](lib/parser.js#L206) Get parser `type`. **Params** * `type` **{String}** **Example** ```js var fn = parser.get('slash'); ``` ### [.push](lib/parser.js#L229) Push a node onto the stack for the given `type`. **Params** * `type` **{String}** * `returns` **{Object}** `token` **Example** ```js parser.set('all', function() { var match = this.match(/^./); if (match) { var node = this.node(match[0]); this.push(node); return node; } }); ``` ### [.pop](lib/parser.js#L261) Pop a token off of the stack of the given `type`. **Params** * `type` **{String}** * `returns` **{Object}**: Returns a token **Example** ```js parser.set('close', function() { var match = this.match(/^\}/); if (match) { var node = this.node({ type: 'close', val: match[0] }); this.pop(node.type); return node; } }); ``` ### [.isInside](lib/parser.js#L294) Return true if inside a "set" of the given `type`. Sets are created manually by adding a type to `parser.sets`. A node is "inside" a set when an `*.open` node for the given `type` was previously pushed onto the set. The type is removed from the set by popping it off when the `*.close` node for the given type is reached. **Params** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js parser.set('close', function() { var pos = this.position(); var m = this.match(/^\}/); if (!m) return; if (!this.isInside('bracket')) { throw new Error('missing opening bracket'); } }); ``` ### [.isType](lib/parser.js#L324) Return true if `node` is the given `type`. **Params** * `node` **{Object}** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js parser.isType(node, 'brace'); ``` ### [.prev](lib/parser.js#L340) Get the previous AST node from the `parser.stack` (when inside a nested context) or `parser.nodes`. * `returns` **{Object}** **Example** ```js var prev = this.prev(); ``` ### [.prev](lib/parser.js#L394) Match `regex`, return captures, and update the cursor position by `match[0]` length. **Params** * `regex` **{RegExp}** * `returns` **{Object}** **Example** ```js // make sure to use the starting regex boundary: "^" var match = this.match(/^\./); ``` **Params** * `input` **{String}** * `returns` **{Object}**: Returns an AST with `ast.nodes` **Example** ```js var ast = parser.parse('foo/bar'); ``` ### [Compiler](lib/compiler.js#L24) Create a new `Compiler` with the given `options`. **Params** * `options` **{Object}** * `state` **{Object}**: Optionally pass a "state" object to use inside visitor functions. **Example** ```js var Snapdragon = require('snapdragon'); var Compiler = Snapdragon.Compiler; var compiler = new Compiler(); ``` ### [.error](lib/compiler.js#L67) Throw a formatted error message with details including the cursor position. **Params** * `msg` **{String}**: Message to use in the Error. * `node` **{Object}** * `returns` **{undefined}** **Example** ```js compiler.set('foo', function(node) { if (node.val !== 'foo') { throw this.error('expected node.val to be "foo"', node); } }); ``` ### [.emit](lib/compiler.js#L86) Concat the given string to `compiler.output`. **Params** * `string` **{String}** * `node` **{Object}**: Optionally pass the node to use for position if source maps are enabled. * `returns` **{String}**: returns the string **Example** ```js compiler.set('foo', function(node) { this.emit(node.val, node); }); ``` ### [.noop](lib/compiler.js#L104) Emit an empty string to effectively "skip" the string for the given `node`, but still emit the position and node type. **Params** * **{Object}**: node **Example** ```js // example: do nothing for beginning-of-string snapdragon.compiler.set('bos', compiler.noop); ``` ### [.define](lib/compiler.js#L124) Define a non-enumberable property on the `Compiler` instance. This is useful in plugins, for exposing methods inside handlers. **Params** * `key` **{String}**: propery name * `val` **{any}**: property value * `returns` **{Object}**: Returns the Compiler instance for chaining. **Example** ```js compiler.define('customMethod', function() { // do stuff }); ``` ### [.set](lib/compiler.js#L152) Add a compiler `fn` for the given `type`. Compilers are called when the `.compile` method encounters a node of the given type to generate the output string. **Params** * `type` **{String}** * `fn` **{Function}** **Example** ```js compiler .set('comma', function(node) { this.emit(','); }) .set('dot', function(node) { this.emit('.'); }) .set('slash', function(node) { this.emit('/'); }); ``` ### [.get](lib/compiler.js#L168) Get the compiler of the given `type`. **Params** * `type` **{String}** **Example** ```js var fn = compiler.get('slash'); ``` ### [.visit](lib/compiler.js#L188) Visit `node` using the registered compiler function associated with the `node.type`. **Params** * `node` **{Object}** * `returns` **{Object}**: returns the node **Example** ```js compiler .set('i', function(node) { this.visit(node); }) ``` ### [.mapVisit](lib/compiler.js#L226) Iterate over `node.nodes`, calling [visit](#visit) on each node. **Params** * `node` **{Object}** * `returns` **{Object}**: returns the node **Example** ```js compiler .set('i', function(node) { utils.mapVisit(node); }) ``` ### [.compile](lib/compiler.js#L250) Compile the given `AST` and return a string. Iterates over `ast.nodes` with [mapVisit](#mapVisit). **Params** * `ast` **{Object}** * `options` **{Object}**: Compiler options * `returns` **{Object}**: returns the node **Example** ```js var ast = parser.parse('foo'); var str = compiler.compile(ast); ``` ## Snapdragon in the wild A few of the libraries that use snapdragon: * [braces](https://www.npmjs.com/package/braces): Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support… [more](https://github.com/micromatch/braces) | [homepage](https://github.com/micromatch/braces "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.") * [breakdance](https://www.npmjs.com/package/breakdance): Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy… [more](http://breakdance.io) | [homepage](http://breakdance.io "Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy to use. It's time for your markup to get down.") * [expand-brackets](https://www.npmjs.com/package/expand-brackets): Expand POSIX bracket expressions (character classes) in glob patterns. | [homepage](https://github.com/jonschlinkert/expand-brackets "Expand POSIX bracket expressions (character classes) in glob patterns.") * [extglob](https://www.npmjs.com/package/extglob): Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob… [more](https://github.com/micromatch/extglob) | [homepage](https://github.com/micromatch/extglob "Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob patterns.") * [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/micromatch/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.") * [nanomatch](https://www.npmjs.com/package/nanomatch): Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash… [more](https://github.com/micromatch/nanomatch) | [homepage](https://github.com/micromatch/nanomatch "Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash 4.3 wildcard support only (no support for exglobs, posix brackets or braces)") ## History ### v0.9.0 **Breaking changes!** In an attempt to make snapdragon lighter, more versatile, and more pluggable, some major changes were made in this release. * `parser.capture` was externalized to [snapdragon-capture](https://github.com/jonschlinkert/snapdragon-capture) * `parser.capturePair` was externalized to [snapdragon-capture-set](https://github.com/jonschlinkert/snapdragon-capture-set) * Nodes are now an instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node) ### v0.5.0 **Breaking changes!** Substantial breaking changes were made in v0.5.0! Most of these changes are part of a larger refactor that will be finished in 0.6.0, including the introduction of a `Lexer` class. * Renderer was renamed to `Compiler` * the `.render` method was renamed to `.compile` ## About
Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
Running Tests Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: ```sh $ npm install && npm test ```
Building docs _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ To generate the readme, run the following command: ```sh $ npm install -g verbose/verb#dev verb-generate-readme && verb ```
### Related projects A few of the libraries that use snapdragon: * [snapdragon-capture-set](https://www.npmjs.com/package/snapdragon-capture-set): Plugin that adds a `.captureSet()` method to snapdragon, for matching and capturing substrings that have… [more](https://github.com/jonschlinkert/snapdragon-capture-set) | [homepage](https://github.com/jonschlinkert/snapdragon-capture-set "Plugin that adds a `.captureSet()` method to snapdragon, for matching and capturing substrings that have an `open` and `close`, like braces, brackets, etc") * [snapdragon-capture](https://www.npmjs.com/package/snapdragon-capture): Snapdragon plugin that adds a capture method to the parser instance. | [homepage](https://github.com/jonschlinkert/snapdragon-capture "Snapdragon plugin that adds a capture method to the parser instance.") * [snapdragon-node](https://www.npmjs.com/package/snapdragon-node): Snapdragon utility for creating a new AST node in custom code, such as plugins. | [homepage](https://github.com/jonschlinkert/snapdragon-node "Snapdragon utility for creating a new AST node in custom code, such as plugins.") * [snapdragon-util](https://www.npmjs.com/package/snapdragon-util): Utilities for the snapdragon parser/compiler. | [homepage](https://github.com/here-be/snapdragon-util "Utilities for the snapdragon parser/compiler.") ### Contributors | **Commits** | **Contributor** | | --- | --- | | 156 | [jonschlinkert](https://github.com/jonschlinkert) | | 3 | [doowb](https://github.com/doowb) | | 2 | [danez](https://github.com/danez) | | 1 | [EdwardBetts](https://github.com/EdwardBetts) | ### Author **Jon Schlinkert** * [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) * [GitHub Profile](https://github.com/jonschlinkert) * [Twitter Profile](https://twitter.com/jonschlinkert) ### License Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT License](LICENSE). *** _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on March 20, 2018._snapdragon-0.12.1/docs/000077500000000000000000000000001403404360400146555ustar00rootroot00000000000000snapdragon-0.12.1/docs/compiling.md000066400000000000000000000007671403404360400171720ustar00rootroot00000000000000# Compiling with snapdragon
Pre-requisites If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone. To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
snapdragon-0.12.1/docs/core-concepts.md000066400000000000000000000013301403404360400177400ustar00rootroot00000000000000WIP (draft) # Core concepts - [Lexer](#parser) * Token Stream * Token * Scope - [Parser](#parser) * [Node](#node) * Stack * [AST](#ast) - [Compiler](#compiler) * State - [Renderer](#renderer) * Contexts * Context ## Lexer - [ ] Token - [ ] Tokens - [ ] Scope ## Parser ### AST TODO ### Node #### Properties Officially supported properties - `type` - `val` - `nodes` **Related** - The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value. - The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node. ## Compiler TODO ## Renderer TODO [verb][] snapdragon-0.12.1/docs/crash-course.md000066400000000000000000000060451403404360400176020ustar00rootroot00000000000000WIP (draft) ## Crash course ### Parser The parser's job is create an AST from a string. It does this by looping over registered parser-middleware to create nodes from captured substrings. **Parsing** When a middleware returns a node, the parser updates the string position and starts over again with the first middleware. **Parser middleware** Each parser-middleware is responsible for matching and capturing a specific "type" of substring, and optionally returning a `node` with information about what was captured. **Node** A `node` is an object that is used for storing information about a captured substring, or to mark a significant point or delimiter in the AST or string. The only required property is `node.type`. Every node has a `node.type` that semantically describes a substring that was captured by a middleware - or some other purpose of the node, along with any other information that might be useful later during parsing or compiling. of a specific `node.type` that semantically describes the capturing substrings . Matching is typically performed using a regular expression, but any means can be used. Upon capturing a substring, the parser-middleware - capturing and/or further processing relevant part(s) of the captured substring - returning a node with information that semantically describes the substring that was captured, along with When a parser returns a node, that indicates by calling each user-defined middleware (referred to as "parsers") until one returns a node. Each parser middleware middleware a string and calling user-defined "parsers" **AST** which is an object with "nodes", where each "node" is an object with a `type` **Nodes** A `node` is an object that is used for storing and describing information about a captured substring. Every node in the AST has a `type` property, and either: - `val`: a captured substring - `nodes`: an array of child nodes When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST. **Example** The absolute simplest AST for a single-character string might look something like this: ```js var ast = { type: 'root', nodes: [ { type: 'text', val: 'a' } ] }; ``` Nodes may have any additional properties, but they must have Parsers and compilers have a one-to-one relationship. The parser uses middleware for Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning. ### Compiler The compiler's job is to render a string. It does this by iterating over an AST, and using the information contained in each node to determine what to render. **A compiler for every parser** Parsers and compilers have a one-to-one relationship. The parser uses middleware for snapdragon-0.12.1/docs/getting-started.md000066400000000000000000000014371403404360400203110ustar00rootroot00000000000000WIP (draft) # Getting started [What is snapdragon, and who created it?](overview.html) - Installing snapdragon - Basic usage - Next steps ## Installing snapdragon ## Usage documentation **Learn how to use snapdragon** The following documentation tells you how to download and start using snapdragon. If you're intestested in creating snapdragon plugins, or you want to understand more about how snapdragon works you can find links to [developer documentation](#developer-documentation) below. is API-focused how to the API methods that are ## Developer documentation **Learn how to create plugins or hack on snapdragon** In the developer documentation, you will learn how Snapdragon works "under the hood" and how to create plugins. If you're more interested in test driving snapdragon, snapdragon-0.12.1/docs/options.md000066400000000000000000000000271403404360400166710ustar00rootroot00000000000000# Options WIP (draft) snapdragon-0.12.1/docs/overview.md000066400000000000000000000032331403404360400170460ustar00rootroot00000000000000WIP (draft) # Overview Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome. ## What is snapdragon? At its heart, snapdragon does two things: - Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST - Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string. **Plugins** ## What can snapdragon do? You can use snapdragon to parse and convert a string into something entirely different, or use it to create "formatters" for beautifying code or plain text. **In the wild** Here's how some real projects are using snapdragon: * [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]: * [micromatch][]: uses snapdragon to create regex from glob patterns * [extglob][]: uses snapdragon to create regex from glob patterns * [braces][]: uses snapdragon to create regex for bash-like brace-expansion * [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions) ## About Snapdragon was created by, [Jon Schlinkert](https://github.com/jonschlinkert), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects. If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to: - follow me on [github]() for notifications and updates about my github projects - follow me on [twitter]() - connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert) snapdragon-0.12.1/docs/parsing.md000066400000000000000000000041251403404360400166440ustar00rootroot00000000000000WIP (draft) # Parsing with snapdragon
Pre-requisites If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone. To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
Table of contents - Usage - Developer * Parser * Parsers * Custom parsers
## API ## Parser The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string. To understand what `Parser` does, The snapdragon parser takes a string and creates an by 1. looping over the string 1. invoking registered [parsers](#parsers) to create new AST nodes. The following documentation describes this in more detail. checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and: * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function) * if _no matches are found_, an error is throw notifying you that the s ## Parsers Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string. **How parsers work** A very basic parser function might look something like this: ```js function() { var parsed = this.parsed; var pos = this.position(); var m = this.match(regex); if (!m || !m[0]) return; var prev = this.prev(); var node = pos({ type: type, val: m[0] }); define(node, 'match', m); define(node, 'inside', this.stack.length > 0); define(node, 'parent', prev); define(node, 'parsed', parsed); define(node, 'rest', this.input); prev.nodes.push(node); } ``` TODO ## Custom parsers TODO ## Plugins TODO ```js parser.use(function() {}); ``` ```js snapdragon.parser.use(function() {}); ``` snapdragon-0.12.1/docs/plugins.md000066400000000000000000000004751403404360400166660ustar00rootroot00000000000000WIP (draft) # Snapdragon plugins ```js var snapdragon = new Snapdgragon(); // register plugins snapdragon.use(function() {}); // register parser plugins snapdragon.parser.use(function() {}); // register compiler plugins snapdragon.compiler.use(function() {}); // parse var ast = snapdragon.parse('foo/bar'); ``` snapdragon-0.12.1/examples/000077500000000000000000000000001403404360400155435ustar00rootroot00000000000000snapdragon-0.12.1/examples/dot.js000066400000000000000000000013111403404360400166630ustar00rootroot00000000000000var Snapdragon = require('..'); var snapdragon = new Snapdragon(); var ast = snapdragon.parser .set('dot', function () { var pos = this.position(); var m = this.match(/^\./); if (!m) return; return pos({ // define the `type` of compiler to use // setting this value is optional, since the // parser will add it based on the name used // when registering the handler, but it's // good practice since tokens aren't always // returned type: 'dot', val: m[0] }) }) .parse('.') var result = snapdragon.compiler .set('dot', function (node) { return this.emit('\\' + node.val); }) .compile(ast) console.log(result.output); //=> '\.' snapdragon-0.12.1/examples/errors.js000066400000000000000000000011601403404360400174130ustar00rootroot00000000000000'use strict'; var Parser = require('../lib/parser'); var parser = new Parser() .set('at', function() { var pos = this.position(); var match = this.match(/^@/); if (match) { return pos({val: match[0]}); } }) .set('slash', function() { var pos = this.position(); var match = this.match(/^\//); if (match) { return pos({val: match[0]}); } }) .set('text', function() { var pos = this.position(); var match = this.match(/^\w+/); if (match) { return pos({val: match[0]}); } }) var ast = parser.parse('git@github.com:foo/bar.git'); console.log(ast); snapdragon-0.12.1/examples/guide-examples.js000066400000000000000000000002121403404360400210050ustar00rootroot00000000000000var Snapdragon = require('..'); var snapdragon = new Snapdragon(); /** * */ var ast = snapdragon.parse('foo/*.js'); console.log(ast); snapdragon-0.12.1/examples/parser.js000066400000000000000000000016511403404360400174000ustar00rootroot00000000000000'use strict'; var Parser = require('../lib/parser'); var parser = new Parser() .set('at', function() { var pos = this.position(); var match = this.match(/^@/); if (match) { return pos({val: match[0]}); } }) .set('slash', function() { var pos = this.position(); var match = this.match(/^\//); if (match) { return pos({val: match[0]}); } }) .set('text', function() { var pos = this.position(); var match = this.match(/^\w+/); if (match) { return pos({val: match[0]}); } }) .set('dot', function() { var pos = this.position(); var match = this.match(/^\./); if (match) { return pos({val: match[0]}); } }) .set('colon', function() { var pos = this.position(); var match = this.match(/^:/); if (match) { return pos({val: match[0]}); } }) var ast = parser.parse('git@github.com:foo/bar.git'); console.log(ast); snapdragon-0.12.1/examples/tiny-globs.js000066400000000000000000000015131403404360400201700ustar00rootroot00000000000000'use strict'; var Snapdragon = require('..'); var Snapdragon = new Snapdragon(); /** * 1 */ // var parser = new Parser(); // console.log(parser.parse('foo/*.js')); /** * 2 */ snapdragon.parser .set('text', function() { var pos = this.position(); var m = this.match(/^\w+/); if (m) { return pos(this.node(m[0])); } }) .set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (m) { return pos(this.node(m[0])); } }) .set('star', function() { var pos = this.position(); var m = this.match(/^\*/); if (m) { return pos(this.node(m[0])); } }) .set('dot', function() { var pos = this.position(); var m = this.match(/^\./); if (m) { return pos(this.node(m[0])); } }); console.log(parser.parse('foo/*.js')); snapdragon-0.12.1/gulpfile.js000066400000000000000000000015101403404360400160670ustar00rootroot00000000000000'use strict'; var gulp = require('gulp'); var istanbul = require('gulp-istanbul'); var eslint = require('gulp-eslint'); var mocha = require('gulp-mocha'); var unused = require('gulp-unused'); gulp.task('coverage', function() { return gulp.src(['lib/*.js', 'index.js']) .pipe(istanbul({includeUntested: true})) .pipe(istanbul.hookRequire()); }); gulp.task('mocha', ['coverage'], function() { return gulp.src(['test/*.js']) .pipe(mocha()) .pipe(istanbul.writeReports()); }); gulp.task('eslint', function() { return gulp.src(['*.js', 'lib/*.js', 'test/*.js']) .pipe(eslint()) .pipe(eslint.format()); }); gulp.task('unused', function() { return gulp.src(['index.js', 'lib/*.js']) .pipe(unused({keys: Object.keys(require('./lib/utils.js'))})); }); gulp.task('default', ['coverage', 'eslint', 'mocha']); snapdragon-0.12.1/index.js000066400000000000000000000123121403404360400153710ustar00rootroot00000000000000'use strict'; var define = require('define-property'); var extend = require('extend-shallow'); var Compiler = require('./lib/compiler'); var Parser = require('./lib/parser'); /** * Create a new instance of `Snapdragon` with the given `options`. * * ```js * var Snapdragon = require('snapdragon'); * var snapdragon = new Snapdragon(); * ``` * @param {Object} `options` * @api public */ function Snapdragon(options) { if (typeof options === 'string') { var protoa = Object.create(Snapdragon.prototype); Snapdragon.call(protoa); return protoa.render.apply(protoa, arguments); } if (!(this instanceof Snapdragon)) { var protob = Object.create(Snapdragon.prototype); Snapdragon.call(protob); return protob; } this.define('cache', {}); this.options = extend({source: 'string'}, options); this.isSnapdragon = true; this.plugins = { fns: [], preprocess: [], visitors: {}, before: {}, after: {} }; } /** * Register a plugin `fn`. * * ```js * var snapdragon = new Snapdgragon([options]); * snapdragon.use(function() { * console.log(this); //<= snapdragon instance * console.log(this.parser); //<= parser instance * console.log(this.compiler); //<= compiler instance * }); * ``` * @param {Object} `fn` * @api public */ Snapdragon.prototype.use = function(fn) { fn.call(this, this); return this; }; /** * Define a non-enumerable property or method on the Snapdragon instance. * Useful in plugins for adding convenience methods that can be used in * nodes. * * ```js * snapdraong.define('isTypeFoo', function(node) { * return node.type === 'foo'; * }); * * // inside a handler * snapdragon.set('razzle-dazzle', function(node) { * if (this.isTypeFoo(node.parent)) { * // do stuff * } * }); * ``` * @param {String} `name` Name of the property or method being defined * @param {any} `val` Property value * @return {Object} Returns the instance for chaining. * @api public */ Snapdragon.prototype.define = function(key, val) { define(this, key, val); return this; }; /** * Parse the given `str` and return an AST. * * ```js * var snapdragon = new Snapdgragon([options]); * var ast = snapdragon.parse('foo/bar'); * console.log(ast); * ``` * @param {String} `str` * @param {Object} `options` Set `options.sourcemap` to true to enable source maps. * @return {Object} Returns an AST. * @api public */ Snapdragon.prototype.parse = function(str, options) { var opts = extend({}, this.options, options); var ast = this.parser.parse(str, opts); // add non-enumerable parser reference to AST define(ast, 'parser', this.parser); return ast; }; /** * Compile an `ast` returned from `snapdragon.parse()` * * ```js * // compile * var res = snapdragon.compile(ast); * // get the compiled output string * console.log(res.output); * ``` * @param {Object} `ast` * @param {Object} `options` * @return {Object} Returns an object with an `output` property with the rendered string. * @api public */ Snapdragon.prototype.compile = function(ast, options) { var opts = extend({}, this.options, options); return this.compiler.compile(ast, opts); }; /** * Renders the given string or AST by calling `snapdragon.parse()` (if it's a string) * then `snapdragon.compile()`, and returns the output string. * * ```js * // setup parsers and compilers, then call render * var str = snapdragon.render([string_or_ast]); * console.log(str); * ``` * @param {Object} `ast` * @param {Object} `options` * @return {Object} Returns an object with an `output` property with the rendered string. * @api public */ Snapdragon.prototype.render = function(ast, options) { if (typeof ast === 'string') { ast = this.parse(ast, options); } return this.compile(ast, options).output; }; /** * Get or set a `Snapdragon.Compiler` instance. * @api public */ Object.defineProperty(Snapdragon.prototype, 'compiler', { configurable: true, set: function(val) { this.cache.compiler = val; }, get: function() { if (!this.cache.compiler) { this.cache.compiler = new Compiler(this.options); } return this.cache.compiler; } }); /** * Get or set a `Snapdragon.Parser` instance. * @api public */ Object.defineProperty(Snapdragon.prototype, 'parser', { configurable: true, set: function(val) { this.cache.parser = val; }, get: function() { if (!this.cache.parser) { this.cache.parser = new Parser(this.options); } return this.cache.parser; } }); /** * Get the compilers from a `Snapdragon.Compiler` instance. * @api public */ Object.defineProperty(Snapdragon.prototype, 'compilers', { get: function() { return this.compiler.compilers; } }); /** * Get the parsers from a `Snapdragon.Parser` instance. * @api public */ Object.defineProperty(Snapdragon.prototype, 'parsers', { get: function() { return this.parser.parsers; } }); /** * Get the regex cache from a `Snapdragon.Parser` instance. * @api public */ Object.defineProperty(Snapdragon.prototype, 'regex', { get: function() { return this.parser.regex; } }); /** * Expose `Parser` and `Compiler` */ Snapdragon.Compiler = Compiler; Snapdragon.Parser = Parser; /** * Expose `Snapdragon` */ module.exports = Snapdragon; snapdragon-0.12.1/lib/000077500000000000000000000000001403404360400144735ustar00rootroot00000000000000snapdragon-0.12.1/lib/compiler.js000066400000000000000000000140061403404360400166440ustar00rootroot00000000000000'use strict'; var use = require('use'); var util = require('snapdragon-util'); var Emitter = require('component-emitter'); var define = require('define-property'); var extend = require('extend-shallow'); var error = require('./error'); /** * Create a new `Compiler` with the given `options`. * * ```js * var Snapdragon = require('snapdragon'); * var Compiler = Snapdragon.Compiler; * var compiler = new Compiler(); * ``` * @param {Object} `options` * @param {Object} `state` Optionally pass a "state" object to use inside visitor functions. * @api public */ function Compiler(options, state) { this.options = extend({source: 'string'}, options); this.emitter = new Emitter(); this.on = this.emitter.on.bind(this.emitter); this.isCompiler = true; this.state = state || {}; this.state.inside = this.state.inside || {}; this.compilers = {}; this.output = ''; this.indent = ''; this.set('eos', function(node) { return this.emit(node.val, node); }); this.set('bos', function(node) { return this.emit(node.val, node); }); use(this); } /** * Prototype methods */ Compiler.prototype = { /** * Throw a formatted error message with details including the cursor position. * * ```js * compiler.set('foo', function(node) { * if (node.val !== 'foo') { * throw this.error('expected node.val to be "foo"', node); * } * }); * ``` * @name .error * @param {String} `msg` Message to use in the Error. * @param {Object} `node` * @return {undefined} * @api public */ error: function(/*msg, node*/) { return error.apply(this, arguments); }, /** * Concat the given string to `compiler.output`. * * ```js * compiler.set('foo', function(node) { * this.emit(node.val, node); * }); * ``` * @name .emit * @param {String} `string` * @param {Object} `node` Optionally pass the node to use for position if source maps are enabled. * @return {String} returns the string * @api public */ emit: function(val, node) { this.output += val; return val; }, /** * Emit an empty string to effectively "skip" the string for the given `node`, * but still emit the position and node type. * * ```js * // example: do nothing for beginning-of-string * snapdragon.compiler.set('bos', compiler.noop); * ``` * @name .noop * @param {Object} node * @api public */ noop: function(node) { this.emit('', node); }, /** * Define a non-enumberable property on the `Compiler` instance. This is useful * in plugins, for exposing methods inside handlers. * * ```js * compiler.define('customMethod', function() { * // do stuff * }); * ``` * @name .define * @param {String} `key` propery name * @param {any} `val` property value * @return {Object} Returns the Compiler instance for chaining. * @api public */ define: function(key, val) { define(this, key, val); return this; }, /** * Add a compiler `fn` for the given `type`. Compilers are called * when the `.compile` method encounters a node of the given type to * generate the output string. * * ```js * compiler * .set('comma', function(node) { * this.emit(','); * }) * .set('dot', function(node) { * this.emit('.'); * }) * .set('slash', function(node) { * this.emit('/'); * }); * ``` * @name .set * @param {String} `type` * @param {Function} `fn` * @api public */ set: function(type, fn) { this.compilers[type] = fn; return this; }, /** * Get the compiler of the given `type`. * * ```js * var fn = compiler.get('slash'); * ``` * @name .get * @param {String} `type` * @api public */ get: function(type) { return this.compilers[type]; }, /** * Visit `node` using the registered compiler function associated with the * `node.type`. * * ```js * compiler * .set('i', function(node) { * this.visit(node); * }) * ``` * @name .visit * @param {Object} `node` * @return {Object} returns the node * @api public */ visit: function(node) { if (util.isOpen(node)) { util.addType(this.state, node); } this.emitter.emit('node', node); var fn = this.compilers[node.type] || this.compilers.unknown; if (typeof fn !== 'function') { throw this.error('compiler "' + node.type + '" is not registered', node); } var val = fn.call(this, node) || node; if (util.isNode(val)) { node = val; } if (util.isClose(node)) { util.removeType(this.state, node); } return node; }, /** * Iterate over `node.nodes`, calling [visit](#visit) on each node. * * ```js * compiler * .set('i', function(node) { * utils.mapVisit(node); * }) * ``` * @name .mapVisit * @param {Object} `node` * @return {Object} returns the node * @api public */ mapVisit: function(parent) { var nodes = parent.nodes || parent.children; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (!node.parent) node.parent = parent; nodes[i] = this.visit(node) || node; } }, /** * Compile the given `AST` and return a string. Iterates over `ast.nodes` * with [mapVisit](#mapVisit). * * ```js * var ast = parser.parse('foo'); * var str = compiler.compile(ast); * ``` * @name .compile * @param {Object} `ast` * @param {Object} `options` Compiler options * @return {Object} returns the node * @api public */ compile: function(ast, options) { var opts = extend({}, this.options, options); this.ast = ast; this.output = ''; // source map support if (opts.sourcemap) { var sourcemaps = require('./source-maps'); sourcemaps(this); this.mapVisit(this.ast); this.applySourceMaps(); this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON(); } else { this.mapVisit(this.ast); } return this; } }; /** * Expose `Compiler` */ module.exports = Compiler; snapdragon-0.12.1/lib/error.js000066400000000000000000000010371403404360400161630ustar00rootroot00000000000000'use strict'; var get = require('get-value'); module.exports = function(msg, node) { node = node || {}; var pos = node.position || {}; var line = get(node, 'position.end.line') || 1; var column = get(node, 'position.end.column') || 1; var source = this.options.source; var message = source + ' : ' + msg; var err = new Error(message); err.source = source; err.reason = msg; err.pos = pos; if (this.options.silent) { this.errors.push(err); } else { throw err; } }; snapdragon-0.12.1/lib/parser.js000066400000000000000000000313371403404360400163340ustar00rootroot00000000000000'use strict'; var use = require('use'); var util = require('snapdragon-util'); var Cache = require('map-cache'); var Node = require('snapdragon-node'); var define = require('define-property'); var extend = require('extend-shallow'); var Emitter = require('component-emitter'); var isObject = require('isobject'); var Position = require('./position'); var error = require('./error'); /** * Create a new `Parser` with the given `input` and `options`. * * ```js * var Snapdragon = require('snapdragon'); * var Parser = Snapdragon.Parser; * var parser = new Parser(); * ``` * @param {String} `input` * @param {Object} `options` * @api public */ function Parser(options) { this.options = extend({source: 'string'}, options); this.isParser = true; this.Node = Node; this.init(this.options); use(this); } /** * Prototype methods */ Parser.prototype = Emitter({ constructor: Parser, init: function(options) { this.orig = ''; this.input = ''; this.parsed = ''; this.currentType = 'root'; this.setCount = 0; this.count = 0; this.column = 1; this.line = 1; this.regex = new Cache(); this.errors = this.errors || []; this.parsers = this.parsers || {}; this.types = this.types || []; this.sets = this.sets || {}; this.fns = this.fns || []; this.tokens = []; this.stack = []; this.typeStack = []; this.setStack = []; var pos = this.position(); this.bos = pos(this.node({ type: 'bos', val: '' })); this.ast = pos(this.node({ type: this.options.astType || 'root', errors: this.errors })); this.ast.pushNode(this.bos); this.nodes = [this.ast]; }, /** * Throw a formatted error message with details including the cursor position. * * ```js * parser.set('foo', function(node) { * if (node.val !== 'foo') { * throw this.error('expected node.val to be "foo"', node); * } * }); * ``` * @name .error * @param {String} `msg` Message to use in the Error. * @param {Object} `node` * @return {undefined} * @api public */ error: function(/*msg, node*/) { return error.apply(this, arguments); }, /** * Define a non-enumberable property on the `Parser` instance. This is useful * in plugins, for exposing methods inside handlers. * * ```js * parser.define('foo', 'bar'); * ``` * @name .define * @param {String} `key` propery name * @param {any} `val` property value * @return {Object} Returns the Parser instance for chaining. * @api public */ define: function(key, val) { define(this, key, val); return this; }, /** * Create a new [Node](#node) with the given `val` and `type`. * * ```js * parser.node('/', 'slash'); * ``` * @name .node * @param {Object} `val` * @param {String} `type` * @return {Object} returns the [Node](#node) instance. * @api public */ node: function(val, type) { return new this.Node(val, type); }, /** * Mark position and patch `node.position`. * * ```js * parser.set('foo', function(node) { * var pos = this.position(); * var match = this.match(/foo/); * if (match) { * // call `pos` with the node * return pos(this.node(match[0])); * } * }); * ``` * @name .position * @return {Function} Returns a function that takes a `node` * @api public */ position: function() { var start = { line: this.line, column: this.column }; var parsed = this.parsed; var self = this; return function(node) { if (!node.isNode) node = new Node(node); node.define('position', new Position(start, self)); node.define('parsed', parsed); node.define('inside', self.stack.length > 0); node.define('rest', self.input); return node; }; }, /** * Add parser `type` with the given visitor `fn`. * * ```js * parser.set('all', function() { * var match = this.match(/^./); * if (match) { * return this.node(match[0]); * } * }); * ``` * @name .set * @param {String} `type` * @param {Function} `fn` * @api public */ set: function(type, fn) { if (this.types.indexOf(type) === -1) { this.types.push(type); } this.parsers[type] = fn.bind(this); return this; }, /** * Get parser `type`. * * ```js * var fn = parser.get('slash'); * ``` * @name .get * @param {String} `type` * @api public */ get: function(type) { return this.parsers[type]; }, /** * Push a node onto the stack for the given `type`. * * ```js * parser.set('all', function() { * var match = this.match(/^./); * if (match) { * var node = this.node(match[0]); * this.push(node); * return node; * } * }); * ``` * @name .push * @param {String} `type` * @return {Object} `token` * @api public */ push: function(type, token) { this.sets[type] = this.sets[type] || []; this.count++; this.stack.push(token); this.setStack.push(token); this.typeStack.push(type); return this.sets[type].push(token); }, /** * Pop a token off of the stack of the given `type`. * * ```js * parser.set('close', function() { * var match = this.match(/^\}/); * if (match) { * var node = this.node({ * type: 'close', * val: match[0] * }); * * this.pop(node.type); * return node; * } * }); * ``` * @name .pop * @param {String} `type` * @returns {Object} Returns a token * @api public */ pop: function(type) { if (this.sets[type]) { this.count--; this.stack.pop(); this.setStack.pop(); this.typeStack.pop(); return this.sets[type].pop(); } }, /** * Return true if inside a "set" of the given `type`. Sets are created * manually by adding a type to `parser.sets`. A node is "inside" a set * when an `*.open` node for the given `type` was previously pushed onto the set. * The type is removed from the set by popping it off when the `*.close` * node for the given type is reached. * * ```js * parser.set('close', function() { * var pos = this.position(); * var m = this.match(/^\}/); * if (!m) return; * if (!this.isInside('bracket')) { * throw new Error('missing opening bracket'); * } * }); * ``` * @name .isInside * @param {String} `type` * @return {Boolean} * @api public */ isInside: function(type) { if (typeof type === 'undefined') { return this.count > 0; } if (!Array.isArray(this.sets[type])) { return false; } return this.sets[type].length > 0; }, isDirectlyInside: function(type) { if (typeof type === 'undefined') { return this.count > 0 ? util.last(this.typeStack) : null; } return util.last(this.typeStack) === type; }, /** * Return true if `node` is the given `type`. * * ```js * parser.isType(node, 'brace'); * ``` * @name .isType * @param {Object} `node` * @param {String} `type` * @return {Boolean} * @api public */ isType: function(node, type) { return node && node.type === type; }, /** * Get the previous AST node from the `parser.stack` (when inside a nested * context) or `parser.nodes`. * * ```js * var prev = this.prev(); * ``` * @name .prev * @return {Object} * @api public */ prev: function(n) { return this.stack.length > 0 ? util.last(this.stack, n) : util.last(this.nodes, n); }, /** * Update line and column based on `str`. */ consume: function(len) { this.input = this.input.substr(len); }, /** * Returns the string up to the given `substring`, * if it exists, and advances the cursor position past the substring. */ advanceTo: function(str, i) { var idx = this.input.indexOf(str, i); if (idx !== -1) { var val = this.input.slice(0, idx); this.consume(idx + str.length); return val; } }, /** * Update column based on `str`. */ updatePosition: function(str, len) { var lines = str.match(/\n/g); if (lines) this.line += lines.length; var i = str.lastIndexOf('\n'); this.column = ~i ? len - i : this.column + len; this.parsed += str; this.consume(len); }, /** * Match `regex`, return captures, and update the cursor position by `match[0]` length. * * ```js * // make sure to use the starting regex boundary: "^" * var match = this.match(/^\./); * ``` * @name .prev * @param {RegExp} `regex` * @return {Object} * @api public */ match: function(regex) { var m = regex.exec(this.input); if (m) { this.updatePosition(m[0], m[0].length); return m; } }, /** * Push `node` to `parent.nodes` and assign `node.parent` */ pushNode: function(node, parent) { if (node && parent) { if (parent === node) parent = this.ast; define(node, 'parent', parent); if (parent.nodes) parent.nodes.push(node); if (this.sets.hasOwnProperty(parent.type)) { this.currentType = parent.type; } } }, /** * Capture end-of-string */ eos: function() { if (this.input) return; var pos = this.position(); var prev = this.prev(); while (prev.type !== 'root' && !prev.visited) { if (this.options.strict === true) { throw new SyntaxError('invalid syntax:' + prev); } if (!util.hasOpenAndClose(prev)) { define(prev.parent, 'escaped', true); define(prev, 'escaped', true); } this.visit(prev, function(node) { if (!util.hasOpenAndClose(node.parent)) { define(node.parent, 'escaped', true); define(node, 'escaped', true); } }); prev = prev.parent; } var node = pos(this.node(this.append || '', 'eos')); if (typeof this.options.eos === 'function') { node = this.options.eos.call(this, node); } if (this.parsers.eos) { this.parsers.eos.call(this, node); } define(node, 'parent', this.ast); return node; }, /** * Run parsers to advance the cursor position */ getNext: function() { var parsed = this.parsed; var len = this.types.length; var idx = -1; while (++idx < len) { var type = this.types[idx]; var tok = this.parsers[type].call(this); if (tok === true) { break; } if (tok) { tok.type = tok.type || type; define(tok, 'rest', this.input); define(tok, 'parsed', parsed); this.last = tok; this.tokens.push(tok); this.emit('node', tok); return tok; } } }, /** * Run parsers to get the next AST node */ advance: function() { var input = this.input; this.pushNode(this.getNext(), this.prev()); // if we're here and input wasn't modified, throw an error if (this.input && input === this.input) { var chokedOn = this.input.slice(0, 10); var err = this.error('no parser for: "' + chokedOn, this.last); if (this.hasListeners('error')) { this.emit('error', err); } else { throw err; } } }, /** * Parse the given string an return an AST object. * * ```js * var ast = parser.parse('foo/bar'); * ``` * @param {String} `input` * @return {Object} Returns an AST with `ast.nodes` * @api public */ parse: function(input) { if (typeof input !== 'string') { throw new TypeError('expected a string'); } this.init(this.options); this.orig = input; this.input = input; // run parsers while (this.input) this.advance(); // balance unmatched sets, if not disabled balanceSets(this, this.stack.pop()); // create end-of-string node var eos = this.eos(); var ast = this.prev(); if (ast.type === 'root') { this.pushNode(eos, ast); } return this.ast; }, /** * Visit `node` with the given `fn` */ visit: function(node, fn) { if (!isObject(node) || node.isNode !== true) { throw new Error('expected node to be an instance of Node'); } if (node.visited) return; node.define('visited', true); node = fn(node) || node; if (node.nodes) { this.mapVisit(node.nodes, fn, node); } return node; }, /** * Map visit over array of `nodes`. */ mapVisit: function(nodes, fn, parent) { for (var i = 0; i < nodes.length; i++) { this.visit(nodes[i], fn); } } }); function balanceSets(parser, node) { if (node && parser.options.strict === true) { throw parser.error('imbalanced "' + node.type + '": "' + parser.orig + '"'); } if (node && node.nodes && node.nodes.length) { var first = node.nodes[0]; first.val = '\\' + first.val; } } /** * Expose `Parser` */ module.exports = Parser; snapdragon-0.12.1/lib/position.js000066400000000000000000000003011403404360400166670ustar00rootroot00000000000000'use strict'; /** * Store the position for a node */ module.exports = function Position(start, parser) { this.start = start; this.end = { line: parser.line, column: parser.column }; }; snapdragon-0.12.1/lib/source-maps.js000066400000000000000000000072621403404360400172760ustar00rootroot00000000000000'use strict'; var fs = require('fs'); var path = require('path'); var define = require('define-property'); var sourceMapResolve = require('source-map-resolve'); var SourceMap = require('source-map'); /** * Expose `mixin()`. * This code is based on `source-maps-support.js` in reworkcss/css * https://github.com/reworkcss/css/blob/master/lib/stringify/source-map-support.js * Copyright (c) 2012 TJ Holowaychuk */ module.exports = mixin; /** * Mixin source map support into `compiler`. * * @param {Object} `compiler` * @api public */ function mixin(compiler) { define(compiler, '_comment', compiler.comment); compiler.map = new SourceMap.SourceMapGenerator(); compiler.position = { line: 1, column: 1 }; compiler.content = {}; compiler.files = {}; for (var key in exports) { define(compiler, key, exports[key]); } } /** * Update position. * * @param {String} str */ exports.updatePosition = function(str) { var lines = str.match(/\n/g); if (lines) this.position.line += lines.length; var i = str.lastIndexOf('\n'); this.position.column = ~i ? str.length - i : this.position.column + str.length; }; /** * Emit `str` with `position`. * * @param {String} str * @param {Object} [pos] * @return {String} */ exports.emit = function(str, node) { var position = node.position || {}; var source = position.source; if (source) { if (position.filepath) { source = unixify(position.filepath); } this.map.addMapping({ source: source, generated: { line: this.position.line, column: Math.max(this.position.column - 1, 0) }, original: { line: position.start.line, column: position.start.column - 1 } }); if (position.content) { this.addContent(source, position); } if (position.filepath) { this.addFile(source, position); } } this.updatePosition(str); this.output += str; return str; }; /** * Adds a file to the source map output if it has not already been added * @param {String} `file` * @param {Object} `pos` */ exports.addFile = function(file, position) { if (typeof position.content !== 'string') return; if (Object.prototype.hasOwnProperty.call(this.files, file)) return; this.files[file] = position.content; }; /** * Adds a content source to the source map output if it has not already been added * @param {String} `source` * @param {Object} `position` */ exports.addContent = function(source, position) { if (typeof position.content !== 'string') return; if (Object.prototype.hasOwnProperty.call(this.content, source)) return; this.map.setSourceContent(source, position.content); }; /** * Applies any original source maps to the output and embeds the source file * contents in the source map. */ exports.applySourceMaps = function() { Object.keys(this.files).forEach(function(file) { var content = this.files[file]; this.map.setSourceContent(file, content); if (this.options.inputSourcemaps === true) { var originalMap = sourceMapResolve.resolveSync(content, file, fs.readFileSync); if (originalMap) { var map = new SourceMap.SourceMapConsumer(originalMap.map); var relativeTo = originalMap.sourcesRelativeTo; this.map.applySourceMap(map, file, unixify(path.dirname(relativeTo))); } } }, this); }; /** * Process comments, drops sourceMap comments. * @param {Object} node */ exports.comment = function(node) { if (/^# sourceMappingURL=/.test(node.comment)) { return this.emit('', node.position); } return this._comment(node); }; /** * Convert backslash in the given string to forward slashes */ function unixify(fp) { return fp.split(/\\+/).join('/'); } snapdragon-0.12.1/package.json000066400000000000000000000043101403404360400162110ustar00rootroot00000000000000{ "name": "snapdragon", "description": "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.", "version": "0.12.1", "homepage": "https://github.com/here-be/snapdragon", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "contributors": [ "Brian Woodward (https://twitter.com/doowb)", "Daniel Tschinder (https://github.com/danez)", "Jon Schlinkert (http://twitter.com/jonschlinkert)" ], "repository": "here-be/snapdragon", "bugs": { "url": "https://github.com/here-be/snapdragon/issues" }, "license": "MIT", "files": [ "index.js", "lib" ], "main": "index.js", "engines": { "node": ">=0.10.0" }, "scripts": { "test": "mocha" }, "dependencies": { "component-emitter": "^1.2.1", "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "get-value": "^2.0.6", "isobject": "^3.0.0", "map-cache": "^0.2.2", "snapdragon-node": "^1.0.6", "snapdragon-util": "^4.0.0", "source-map": "^0.5.6", "source-map-resolve": "^0.6.0", "use": "^3.1.0" }, "devDependencies": { "mocha": "^3.2.0", "snapdragon-capture-set": "^1.0.1", "snapdragon-capture": "^0.2.0", "gulp": "^3.9.1", "gulp-istanbul": "^1.1.1", "gulp-eslint": "^3.0.1", "gulp-mocha": "^3.0.1", "gulp-unused": "^0.2.1", "gulp-format-md": "^0.1.11", "verb-generate-readme": "^0.6.0" }, "keywords": [ "lexer", "snapdragon" ], "verb": { "toc": "collapsible", "layout": "default", "tasks": [ "readme" ], "plugins": [ "gulp-format-md" ], "related": { "description": "A few of the libraries that use snapdragon:", "implementations": [ "braces", "breakdance", "expand-brackets", "extglob", "micromatch", "nanomatch" ], "list": [ "snapdragon-capture", "snapdragon-capture-set", "snapdragon-node", "snapdragon-util" ] }, "reflinks": [ "css", "pug", "snapdragon-capture", "snapdragon-capture-set", "snapdragon-node" ], "lint": { "reflinks": true } } } snapdragon-0.12.1/support/000077500000000000000000000000001403404360400154415ustar00rootroot00000000000000snapdragon-0.12.1/support/src/000077500000000000000000000000001403404360400162305ustar00rootroot00000000000000snapdragon-0.12.1/support/src/content/000077500000000000000000000000001403404360400177025ustar00rootroot00000000000000snapdragon-0.12.1/support/src/content/compiling.md000066400000000000000000000007671403404360400222170ustar00rootroot00000000000000# Compiling with snapdragon
Pre-requisites If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone. To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
snapdragon-0.12.1/support/src/content/core-concepts.md000066400000000000000000000013321403404360400227670ustar00rootroot00000000000000WIP (draft) # Core concepts - [Lexer](#parser) * Token Stream * Token * Scope - [Parser](#parser) * [Node](#node) * Stack * [AST](#ast) - [Compiler](#compiler) * State - [Renderer](#renderer) * Contexts * Context ## Lexer - [ ] Token - [ ] Tokens - [ ] Scope ## Parser ### AST TODO ### Node #### Properties Officially supported properties - `type` - `val` - `nodes` **Related** - The [snapdragon-position][] plugin adds support for `node.position`, which patches the `node` with the start and end position of a captured value. - The [snapdragon-scope][] plugin adds support for `node.scope`, which patches the `node` with lexical scope of the node. ## Compiler TODO ## Renderer TODO [verb][] snapdragon-0.12.1/support/src/content/crash-course.md000066400000000000000000000060501403404360400226230ustar00rootroot00000000000000WIP (draft) ## Crash course ### Parser The parser's job is create an AST from a string. It does this by looping over registered parser-middleware to create nodes from captured substrings. **Parsing** When a middleware returns a node, the parser updates the string position and starts over again with the first middleware. **Parser middleware** Each parser-middleware is responsible for matching and capturing a specific "type" of substring, and optionally returning a `node` with information about what was captured. **Node** A `node` is an object that is used for storing information about a captured substring, or to mark a significant point or delimiter in the AST or string. The only required property is `node.type`. Every node has a `node.type` that semantically describes a substring that was captured by a middleware - or some other purpose of the node, along with any other information that might be useful later during parsing or compiling. of a specific `node.type` that semantically describes the capturing substrings . Matching is typically performed using a regular expression, but any means can be used. Upon capturing a substring, the parser-middleware - capturing and/or further processing relevant part(s) of the captured substring - returning a node with information that semantically describes the substring that was captured, along with When a parser returns a node, that indicates by calling each user-defined middleware (referred to as "parsers") until one returns a node. Each parser middleware middleware a string and calling user-defined "parsers" **AST** which is an object with "nodes", where each "node" is an object with a `type` **Nodes** A `node` is an object that is used for storing and describing information about a captured substring. Every node in the AST has a `type` property, and either: - `val`: a captured substring - `nodes`: an array of child nodes When the substring is delimited - by, for example, braces, brackets, parentheses, etc - the `node` will In fact, the AST itself is a `node` with type `root`, and a `nodes` array, which contains all of other nodes on the AST. **Example** The absolute simplest AST for a single-character string might look something like this: ```js var ast = { type: 'root', nodes: [ { type: 'text', val: 'a' } ] }; ``` Nodes may have any additional properties, but they must have Parsers and compilers have a one-to-one relationship. The parser uses middleware for Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning. ### Compiler The compiler's job is to render a string. It does this by iterating over an AST, and using the information contained in each node to determine what to render. **A compiler for every parser** Parsers and compilers have a one-to-one relationship. The parser uses middleware for snapdragon-0.12.1/support/src/content/getting-started.md000066400000000000000000000014661403404360400233400ustar00rootroot00000000000000WIP (draft) # Getting started [What is snapdragon, and who created it?](overview.html) ## Table of contents - Installing snapdragon - Basic usage - Next steps ## Installing snapdragon ## Usage documentation **Learn how to use snapdragon** The following documentation tells you how to download and start using snapdragon. If you're intestested in creating snapdragon plugins, or you want to understand more about how snapdragon works you can find links to [developer documentation](#developer-documentation) below. is API-focused how to the API methods that are ## Developer documentation **Learn how to create plugins or hack on snapdragon** In the developer documentation, you will learn how Snapdragon works "under the hood" and how to create plugins. If you're more interested in test driving snapdragon, snapdragon-0.12.1/support/src/content/guides/000077500000000000000000000000001403404360400211625ustar00rootroot00000000000000snapdragon-0.12.1/support/src/content/guides/creating-your-first-parser.md000066400000000000000000000106571403404360400267240ustar00rootroot00000000000000--- title: Creating your first Snapdragon parser --- This guide will show you how to create a basic parser by starting off with the string we want to parse, and gradually adding the code we need based on feedback from Snapdragon. Let's go! ## Prerequisites Before we dive in, let's make sure you have snapdragon installed and setup properly. ### Install snapdragon You can use either [npm](https://npmjs.com) or [yarn](https://yarnpkg.com/) to install snapdragon: **Install with NPM** ```sh $ npm install snapdragon ``` **Install with yarn** ```sh $ yarn add snapdragon ``` ### Setup snapdragon Create a file in the current working directory named `parser.js` (or whatever you prefer), and add the following code: ```js // add snapdragon using node's "require()" var Snapdragon = require('snapdragon'); // create an instance of Snapdragon. This is the basis for your very own application. var snapdragon = new Snapdragon(); ``` With that out of the way, let's get started on our parser! ## Parsing strategy Feel free to skip this section and jump [straight to the code](#learning-by-doing), or follow along as we discuss our high-level parser strategy and goals. ### Defining success The purpose of this guide isn't to parse something complicated or super-interesting. It's to show you how the parser works. If we accomplish that, then you're only limited by your imagination! **The goal** The string we're going to parse is: `foo/*.js` (a basic glob pattern). _(sidebar: whilst there are numerous approaches one could take to parsing or tokenizing any string, and there are many other factors that would need to be considered, such as escaping, user-defined options, and so on, we're going to keep this simple for illustrative purposes, thus these things fall outside of the scope of this guide)_ It's always good to have a basic parsing strategy before you start. As it relates to glob patterns, our high level strategy might be something like: "I want my parser to be able to differentiate between wildcards (stars in this case), slashes, and non-wildcard strings". Our parser will be considered "successful" once it is able to do these things. ### Begin with the end in mind **The expected result** Our final AST will be an object with "nodes", where each "node" is an object with a `type` that semantically describes a substring that was captured by the parser. Some of the node "types" on our final AST will also roughly end up reflecting the goals we described in our high level strategy. Our strategy gives us a starting point, but it's good to be flexible. In reality our parser might end up doing something completely different than what we expected in the beginning. ## Learning by doing Okay, it's time to start writing code. To parse `foo/*.js` we'll need to figure out how to capture each "type" of substring. Although we won't be doing any compiling in this guide, it will help to understand the role the compiler plays, so that you can factor that into your decisions with the parser. **For every node type, there is a parser and a compiler** The actual approach you use for determining where one substring ends and another begins can be a combination of regex, string position/index, or any other mechanism available to you in javascript. Whatever approach you take, Snapdragon's job is to make it as easy as possible for for you. ** node `type` Snapdragon uses "parsers" are the middleware that to capture substrings. This is what we're going to create next. But instead of thinking about code and what to capture, let's try a different approach and take advantage of snapdragon's error reporting to figure out the next step. Update `parser.js` with the following code: ```js var Snapdragon = require('snapdragon'); var snapdragon = new Snapdragon(); /** * */ var ast = snapdragon.parse('foo/*.js'); console.log(ast); ``` Then run the following command: ```sh $ node parser.js ``` You should see an error message that looks something like the following: ```console Error: string : no parser for: "foo/*.js ``` There are a few important bits of information in this message: - `line:1 column: 1` tells us where in the input string this is happening. It's no surprise that we're getting an error on the very first character of our string. - `no parser for:` tells us that no "parsers" are registered for the substring that follows in the error message. ### snapdragon-0.12.1/support/src/content/options.md000066400000000000000000000000271403404360400217160ustar00rootroot00000000000000# Options WIP (draft) snapdragon-0.12.1/support/src/content/overview.md000066400000000000000000000032701403404360400220740ustar00rootroot00000000000000WIP (draft) # Overview Thanks for visiting the snapdragon documentation! Please [let us know](../../issues) if you find any typos, outdated or incorrect information. Pull requests welcome. ## What is snapdragon? At its heart, snapdragon does two things: - Parsing: the [snapdragon parser](parsing.md) takes a string and converts it to an AST - Compiling: the [snapdragon compiler](compiling.md) takes the AST from the snapdragon parser and converts it to another string. **Plugins** ## What can snapdragon do? You can use snapdragon to parse and convert a string into something entirely different, or use it to create "formatters" for beautifying code or plain text. **In the wild** Here's how some real projects are using snapdragon: * [breakdance][]: uses snapdragon to convert HTML to markdown using an AST from [cheerio][]: * [micromatch][]: uses snapdragon to create regex from glob patterns * [extglob][]: uses snapdragon to create regex from glob patterns * [braces][]: uses snapdragon to create regex for bash-like brace-expansion * [expand-reflinks][]: uses snapdragon to parse and re-write markdown [reference links](http://spec.commonmark.org/0.25/#link-reference-definitions) ## About Snapdragon was created by, [Jon Schlinkert]({%= author.url %}), author of [assemble][], [generate][], [update][], [micromatch][], [remarkable][] and many other node.js projects. If you'd like to learn more about me or my projects, or you want to get in touch, please feel free to: - follow me on [github]({%= author.twitter %}) for notifications and updates about my github projects - follow me on [twitter]({%= author.twitter %}) - connect with me on [linkedin](https://www.linkedin.com/in/jonschlinkert) snapdragon-0.12.1/support/src/content/parsing.md000066400000000000000000000041331403404360400216700ustar00rootroot00000000000000WIP (draft) # Parsing with snapdragon
Pre-requisites If you're not quite sure how an AST works, don't sweat it. Not every programmer needs to interact with an AST, and the first experience with one is daunting for everyone. To get the most from this documentation, we suggest you head over to the [begin/parsers-compilers](https://github.com/begin/parsers-compilers) project to brush up. Within a few minutes you'll know everything you need to proceed!
Table of contents - Usage - Developer * Parser * Parsers * Custom parsers
## API ## Parser The snapdragon [Parser]() class contains all of the functionality and methods that are used for creating an AST from a string. To understand what `Parser` does, The snapdragon parser takes a string and creates an by 1. looping over the string 1. invoking registered [parsers](#parsers) to create new AST nodes. The following documentation describes this in more detail. checking to see if any registered [parsers](#parsers) match the sub-string at the current position, and: * if a parser matches, it is called, possibly resuling in a new AST node (this is up to the parser function) * if _no matches are found_, an error is throw notifying you that the s ## Parsers Snapdragon parsers are functions that are registered by name, and are invoked by the `.parse` method as it loops over the given string. **How parsers work** A very basic parser function might look something like this: ```js function() { var parsed = this.parsed; var pos = this.position(); var m = this.match(regex); if (!m || !m[0]) return; var prev = this.prev(); var node = pos({ type: type, val: m[0] }); define(node, 'match', m); define(node, 'inside', this.stack.length > 0); define(node, 'parent', prev); define(node, 'parsed', parsed); define(node, 'rest', this.input); prev.nodes.push(node); } ``` TODO ## Custom parsers TODO ## Plugins TODO ```js parser.use(function() {}); ``` ```js snapdragon.parser.use(function() {}); ``` snapdragon-0.12.1/support/src/content/plugins.md000066400000000000000000000004751403404360400217130ustar00rootroot00000000000000WIP (draft) # Snapdragon plugins ```js var snapdragon = new Snapdgragon(); // register plugins snapdragon.use(function() {}); // register parser plugins snapdragon.parser.use(function() {}); // register compiler plugins snapdragon.compiler.use(function() {}); // parse var ast = snapdragon.parse('foo/bar'); ``` snapdragon-0.12.1/test/000077500000000000000000000000001403404360400147045ustar00rootroot00000000000000snapdragon-0.12.1/test/compile.js000066400000000000000000000044201403404360400166720ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Compile = require('../lib/compiler'); var Parser = require('../lib/parser'); var compiler; var parser; describe('compiler', function() { beforeEach(function() { compiler = new Compile(); compiler .set('parens.open', function(node) { return this.emit('(', node); }) .set('parens.close', function(node) { return this.emit(')', node); }); parser = new Parser(); parser .set('text', function() { var pos = this.position(); var match = this.match(/^\w+/); if (match) { return pos(this.node(match[0])); } }) .set('slash', function() { var pos = this.position(); var match = this.match(/^\//); if (match) { return pos(this.node(match[0])) } }) .set('parens.open', function() { var pos = this.position(); var match = this.match(/^\(/); if (match) { return pos(this.node(match[0])) } }) .set('parens.close', function() { var pos = this.position(); var match = this.match(/^\)/); if (match) { return pos(this.node(match[0])) } }); }); describe('errors', function(cb) { it('should throw an error when a compiler is missing', function(cb) { try { var ast = parser.parse('a/b/c'); compiler.compile(ast); cb(new Error('expected an error')); } catch (err) { assert(err); assert.equal(err.message, 'string : compiler "text" is not registered'); cb(); } }); }); describe('.compile', function() { beforeEach(function() { compiler .set('text', function(node) { return this.emit(node.val); }) .set('slash', function(node) { return this.emit('-'); }); }); it('should set the result on `output`', function() { var ast = parser.parse('a/b/c'); var res = compiler.compile(ast); assert.equal(res.output, 'a-b-c'); }); it('should compile close without open', function() { var ast = parser.parse('a)'); var res = compiler.compile(ast); assert.equal(res.output, 'a)'); }); }); }); snapdragon-0.12.1/test/compiler.js000066400000000000000000000014401403404360400170530ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Compiler = require('../lib/compiler'); var compiler; describe('compiler', function() { beforeEach(function() { compiler = new Compiler(); }); describe('constructor:', function() { it('should return an instance of Compiler:', function() { assert(compiler instanceof Compiler); }); }); // ensures that we catch and document API changes describe('prototype methods:', function() { var methods = [ 'error', 'set', 'emit', 'visit', 'mapVisit', 'compile' ]; methods.forEach(function(method) { it('should expose the `' + method + '` method', function() { assert.equal(typeof compiler[method], 'function', method); }); }); }); }); snapdragon-0.12.1/test/nodes.js000066400000000000000000000032111403404360400163470ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var captureSet = require('snapdragon-capture-set'); var Parser = require('../lib/parser'); var parser; var ast; describe('parser', function() { beforeEach(function() { parser = new Parser(); parser.use(captureSet()); parser.captureSet('brace', /^\{/, /^\}/); parser.set('text', function() { var pos = this.position(); var match = this.match(/^[^{}]/); if (match) { return pos(this.node(match[0])); } }); parser.set('comma', function() { var pos = this.position(); var match = this.match(/,/); if (match) { return pos(this.node(match[0])); } }); ast = parser.parse('a{b,{c,d},e}f'); }); describe('.isType', function() { it('should return true if "node" is the given "type"', function() { assert(ast.isType('root')); assert(ast.nodes[0].isType('bos')); }); }); describe('.hasType', function() { it('should return true if "node" has the given "type"', function() { assert(ast.hasType('bos')); assert(ast.hasType('eos')); }); }); describe('.first', function() { it('should get the first node in node.nodes', function() { assert(ast.first); assert(ast.first.isType('bos')); }); }); describe('.last', function() { it('should get the last node in node.nodes', function() { assert(ast.last); assert(ast.last.isType('eos')); }); }); describe('.next', function() { it('should get the next node in an array of nodes', function() { // console.log(ast) }); }); }); snapdragon-0.12.1/test/parse.js000066400000000000000000000115571403404360400163650ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var Parser = require('../lib/parser'); var parser; describe('parser', function() { beforeEach(function() { parser = new Parser(); }); describe('errors', function(cb) { it('should throw an error when invalid args are passed to parse', function(cb) { var parser = new Parser(); try { parser.parse(); cb(new Error('expected an error')); } catch (err) { assert(err); assert.equal(err.message, 'expected a string'); cb(); } }); }); describe('bos', function() { it('should set a beginning-of-string node', function() { var parser = new Parser(); parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); var ast = parser.parse('a/b'); assert.equal(ast.nodes[0].type, 'bos'); }); }); describe('eos', function() { it('should set an end-of-string node', function() { var parser = new Parser(); parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); var ast = parser.parse('a/b'); assert.equal(ast.nodes[ast.nodes.length - 1].type, 'eos'); }); }); describe('.set():', function() { it('should register middleware', function() { parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); parser.parse('a/b'); assert(parser.parsers.hasOwnProperty('all')); }); it('should use middleware to parse', function() { parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); parser.parse('a/b'); assert.equal(parser.parsed, 'a/b'); assert.equal(parser.input, ''); }); it('should create ast node:', function() { parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); parser.parse('a/b'); assert.equal(parser.ast.nodes.length, 3); }); it('should be chainable:', function() { parser .set('text', function() { var pos = this.position(); var m = this.match(/^\w+/); if (!m) return; return pos({ type: 'text', val: m[0] }); }) .set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (!m) return; return pos({ type: 'slash', val: m[0] }); }); parser.parse('a/b'); assert.equal(parser.ast.nodes.length, 5); }); }); }); describe('ast', function() { beforeEach(function() { parser = new Parser(); parser .set('text', function() { var pos = this.position(); var m = this.match(/^\w+/); if (!m) return; return pos({ type: 'text', val: m[0] }); }) .set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (!m) return; return pos({ type: 'slash', val: m[0] }); }); }); describe('orig:', function() { it('should add pattern to orig property', function() { parser.parse('a/b'); assert.equal(parser.orig, 'a/b'); }); }); describe('recursion', function() { beforeEach(function() { parser.set('text', function() { var pos = this.position(); var m = this.match(/^\w/); if (!m) return; return pos({ val: m[0] }); }); parser.set('open', function() { var pos = this.position(); var m = this.match(/^\{/); if (!m) return; return pos({ val: m[0] }); }); parser.set('close', function() { var pos = this.position(); var m = this.match(/^\}/); if (!m) return; return pos({ val: m[0] }); }); parser.set('comma', function() { var pos = this.position(); var m = this.match(/,/); if (!m) return; return pos({ val: m[0] }); }); }); it('should set original string on `orig`', function() { parser.parse('a{b,{c,d},e}f'); assert.equal(parser.orig, 'a{b,{c,d},e}f'); }); }); }); snapdragon-0.12.1/test/parser.js000066400000000000000000000042701403404360400165410ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Parser = require('../lib/parser'); var parser; describe('parser', function() { beforeEach(function() { parser = new Parser(); }); describe('constructor:', function() { it('should return an instance of Parser:', function() { assert(parser instanceof Parser); }); }); // ensures that we catch and document API changes describe('prototype methods:', function() { var methods = [ 'updatePosition', 'position', 'error', 'set', 'parse', 'match', 'use' ]; methods.forEach(function(method) { it('should expose the `' + method + '` method', function() { assert.equal(typeof parser[method], 'function'); }); }); }); describe('parsers', function() { beforeEach(function() { parser = new Parser(); }); describe('.set():', function() { it('should register a named middleware', function() { parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); assert(typeof parser.parsers.all === 'function'); }); it('should expose named parsers to middleware:', function() { var count = 0; parser.set('word', function() { var pos = this.position(); var m = this.match(/^\w/); if (!m) return; return pos({ type: 'word', val: m[0] }); }); parser.set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (!m) return; var word = this.parsers.word(); var prev = this.prev(); var node = pos({ type: 'slash', val: m[0] }); if (word && word.type === 'word') { count++; } prev.nodes.push(node); prev.nodes.push(word); }); parser.parse('a/b'); assert.equal(parser.ast.nodes.length, 5); assert.equal(count, 1); }); }); }); }); snapdragon-0.12.1/test/position.js000066400000000000000000000003661403404360400171130ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Position = require('../lib/position'); describe('Position', function() { it('should export a function', function() { assert.equal(typeof Position, 'function'); }); }); snapdragon-0.12.1/test/snapdragon.capture.js000066400000000000000000000036241403404360400210450ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var capture = require('snapdragon-capture'); var snapdragon; describe('.capture (plugin usage)', function() { beforeEach(function() { snapdragon = new Snapdragon(); snapdragon.use(capture()); }); describe('errors', function(cb) { it('should throw an error when invalid args are passed to parse', function(cb) { try { snapdragon.parse(); cb(new Error('expected an error')); } catch (err) { assert(err); assert.equal(err.message, 'expected a string'); cb(); } }); }); describe('.capture():', function() { it('should register a parser', function() { snapdragon.capture('all', /^.*/); snapdragon.parse('a/b'); assert(snapdragon.parsers.hasOwnProperty('all')); }); it('should use middleware to parse', function() { snapdragon.capture('all', /^.*/); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.parsed, 'a/b'); assert.equal(snapdragon.parser.input, ''); }); it('should create ast node:', function() { snapdragon.capture('all', /^.*/); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.ast.nodes.length, 3); }); it('should be chainable:', function() { snapdragon.parser .capture('text', /^\w+/) .capture('slash', /^\//); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.ast.nodes.length, 5); }); }); }); describe('ast', function() { beforeEach(function() { snapdragon = new Snapdragon(); snapdragon.use(capture()); snapdragon .capture('text', /^\w+/) .capture('slash', /^\//); }); describe('orig:', function() { it('should add pattern to orig property', function() { snapdragon.parse('a/b'); assert.equal(snapdragon.parser.orig, 'a/b'); }); }); }); snapdragon-0.12.1/test/snapdragon.compile.js000066400000000000000000000030071403404360400210250ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var snapdragon; var parser; describe('snapdragon.compiler', function() { beforeEach(function() { snapdragon = new Snapdragon(); snapdragon.parser .set('text', function() { var pos = this.position(); var match = this.match(/^\w+/); if (match) { return pos(this.node(match[0])); } }) .set('slash', function() { var pos = this.position(); var match = this.match(/^\//); if (match) { return pos(this.node(match[0])) } }); }); describe('errors', function(cb) { it('should throw an error when a compiler is missing', function(cb) { try { var ast = snapdragon.parse('a/b/c'); snapdragon.compile(ast); cb(new Error('expected an error')); } catch (err) { assert(err); assert.equal(err.message, 'string : compiler "text" is not registered'); cb(); } }); }); describe('snapdragon.compile', function() { beforeEach(function() { snapdragon.compiler .set('text', function(node) { return this.emit(node.val); }) .set('slash', function(node) { return this.emit('-'); }); }); it('should set the result on `output`', function() { var ast = snapdragon.parse('a/b/c'); var res = snapdragon.compile(ast); assert.equal(res.output, 'a-b-c'); }); }); }); snapdragon-0.12.1/test/snapdragon.options.js000066400000000000000000000010041403404360400210630ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); describe('.options', function() { it('should correctly accept and store options in constructor', function() { var snap = new Snapdragon({ a: true, b: null, c: false, d: 'd' }); assert.strictEqual(snap.options['a'], true); assert.strictEqual(snap.options['b'], null); assert.strictEqual(snap.options['c'], false); assert.strictEqual(snap.options['d'], 'd'); }); }); snapdragon-0.12.1/test/snapdragon.parse.js000066400000000000000000000102601403404360400205060ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var snapdragon; describe('parser', function() { beforeEach(function() { snapdragon = new Snapdragon(); }); describe('errors', function(cb) { it('should throw an error when invalid args are passed to parse', function(cb) { try { snapdragon.parse(); cb(new Error('expected an error')); } catch (err) { assert(err); assert.equal(err.message, 'expected a string'); cb(); } }); }); describe('.set():', function() { it('should register middleware', function() { snapdragon.parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); if (!m) return; return pos({ type: 'all', val: m[0] }); }); snapdragon.parse('a/b'); assert(snapdragon.parsers.hasOwnProperty('all')); }); it('should use middleware to parse', function() { snapdragon.parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); return pos({ type: 'all', val: m[0] }); }); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.parsed, 'a/b'); assert.equal(snapdragon.parser.input, ''); }); it('should create ast node:', function() { snapdragon.parser.set('all', function() { var pos = this.position(); var m = this.match(/^.*/); return pos({ type: 'all', val: m[0] }); }); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.ast.nodes.length, 3); }); it('should be chainable:', function() { snapdragon.parser .set('text', function() { var pos = this.position(); var m = this.match(/^\w+/); if (!m) return; return pos({ type: 'text', val: m[0] }); }) .set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (!m) return; return pos({ type: 'slash', val: m[0] }); }); snapdragon.parse('a/b'); assert.equal(snapdragon.parser.ast.nodes.length, 5); }); }); }); describe('ast', function() { beforeEach(function() { snapdragon = new Snapdragon(); snapdragon.parser .set('text', function() { var pos = this.position(); var m = this.match(/^\w+/); if (!m) return; return pos({ type: 'text', val: m[0] }); }) .set('slash', function() { var pos = this.position(); var m = this.match(/^\//); if (!m) return; return pos({ type: 'slash', val: m[0] }); }); }); describe('orig:', function() { it('should add pattern to orig property', function() { snapdragon.parse('a/b'); assert.equal(snapdragon.parser.orig, 'a/b'); }); }); describe('recursion', function() { beforeEach(function() { snapdragon.parser.set('text', function() { var pos = this.position(); var m = this.match(/^\w/); if (!m) return; return pos({ type: 'text', val: m[0] }); }); snapdragon.parser.set('open', function() { var pos = this.position(); var m = this.match(/^{/); if (!m) return; return pos({ type: 'open', val: m[0] }); }); snapdragon.parser.set('close', function() { var pos = this.position(); var m = this.match(/^}/); if (!m) return; return pos({ type: 'close', val: m[0] }); }); snapdragon.parser.set('comma', function() { var pos = this.position(); var m = this.match(/,/); if (!m) return; return pos({ type: 'comma', val: m[0] }); }); }); it('should set original string on `orig`', function() { snapdragon.parse('a{b,{c,d},e}f'); assert.equal(snapdragon.parser.orig, 'a{b,{c,d},e}f'); }); }); }); snapdragon-0.12.1/test/snapdragon.regex.js000066400000000000000000000013501403404360400205060ustar00rootroot00000000000000'use strict'; require('mocha'); var assert = require('assert'); var Snapdragon = require('..'); var capture = require('snapdragon-capture'); var snapdragon; describe('parser', function() { beforeEach(function() { snapdragon = new Snapdragon(); snapdragon.use(capture()); }); describe('.regex():', function() { it('should expose a regex cache with regex from registered parsers', function() { snapdragon.capture('dot', /^\./); snapdragon.capture('text', /^\w+/); snapdragon.capture('all', /^.+/); assert(snapdragon.regex.__data__.hasOwnProperty('dot')); assert(snapdragon.regex.__data__.hasOwnProperty('all')); assert(snapdragon.regex.__data__.hasOwnProperty('text')); }); }); }); snapdragon-0.12.1/verbfile.js000066400000000000000000000007331403404360400160640ustar00rootroot00000000000000'use strict'; module.exports = function(verb) { verb.use(require('verb-generate-readme')); verb.preLayout(/\.md$/, function(file, next) { if (!/(verb|readme)/.test(file.stem)) { file.layout = null; } next(); }); verb.task('docs', function(cb) { return verb.src('support/src/content/*.md', {cwd: __dirname}) .pipe(verb.renderFile('md', {layout: null})) .pipe(verb.dest('docs')) }); verb.task('default', ['docs', 'readme']); };