├── .npmignore ├── examples ├── custom-markers │ ├── output.md │ ├── package.json │ └── index.js ├── fails │ ├── output.md │ ├── package.json │ └── index.js ├── without-messages │ ├── output.md │ ├── package.json │ └── index.js ├── basic │ ├── output.md │ ├── package.json │ └── index.js ├── with-messages │ ├── package.json │ ├── output.md │ └── index.js ├── correct-positions │ ├── package.json │ ├── output.md │ └── index.js ├── from-vfiles-data │ ├── output.md │ ├── package.json │ └── index.js └── with-rehype │ ├── package.json │ ├── output.md │ └── index.js ├── .travis.yml ├── .editorconfig ├── lib ├── visitor.js ├── match.js ├── tokenizer.js └── utils.js ├── package.json ├── license ├── .gitignore ├── index.js ├── readme.md └── test.js /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | .nyc_output 3 | .travis.yml 4 | -------------------------------------------------------------------------------- /examples/custom-markers/output.md: -------------------------------------------------------------------------------- 1 | no issues found 2 | # Example with custom markers 3 | 4 | > here 5 | 6 | -------------------------------------------------------------------------------- /examples/fails/output.md: -------------------------------------------------------------------------------- 1 | 2:3: Could not resolve `data.title` in VFile or Processor. 2 | vfile == null? true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/dubnium' 4 | - 'node' 5 | after_script: npm run report 6 | -------------------------------------------------------------------------------- /examples/without-messages/output.md: -------------------------------------------------------------------------------- 1 | no issues found 2 | # Example using quiet option 3 | 4 | > {{ .subtitle }} 5 | 6 | -------------------------------------------------------------------------------- /examples/basic/output.md: -------------------------------------------------------------------------------- 1 | no issues found 2 | # Example 3 | 4 | - Markdown (string) 5 | - 0 (number) 6 | - false (boolean) 7 | 8 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-basic", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "vfile-reporter": "^5.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/fails/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-fails", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "vfile-reporter": "^5.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/with-messages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-with-messages", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "vfile-reporter": "^5.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/custom-markers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-custom-markers", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "vfile-reporter": "^5.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/with-messages/output.md: -------------------------------------------------------------------------------- 1 | 4:3 warning Could not resolve `data.subtitle` in VFile or Processor. undef-variable variables 2 | 3 | ⚠ 1 warning 4 | # Example with messages 5 | 6 | > {{ subtitle }} 7 | 8 | -------------------------------------------------------------------------------- /examples/correct-positions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-exmaple-correct-positions", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "remark-preset-lint-markdown-style-guide": "^2.1.2", 6 | "vfile-reporter": "^5.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yml] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /examples/from-vfiles-data/output.md: -------------------------------------------------------------------------------- 1 | 8:3 warning Could not resolve `data.subtitle` in VFile or Processor. undef-variable variables 2 | 3 | ⚠ 1 warning 4 | --- 5 | title: Example using vfile's data property 6 | --- 7 | 8 | Example using vfile's data property 9 | 10 | > {{ .subtitle }} 11 | 12 | -------------------------------------------------------------------------------- /examples/from-vfiles-data/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-from-vfiles-data", 3 | "dependencies": { 4 | "remark": "^9.0.0", 5 | "remark-extract-frontmatter": "^2.0.0", 6 | "remark-frontmatter": "^1.3.0", 7 | "vfile-reporter": "^5.0.0", 8 | "yaml": "^1.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-rehype/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables-example-with-rehype", 3 | "dependencies": { 4 | "rehype-document": "^2.2.0", 5 | "rehype-format": "^2.3.0", 6 | "rehype-stringify": "^4.0.0", 7 | "remark": "^9.0.0", 8 | "remark-rehype": "^3.0.1", 9 | "vfile-reporter": "^5.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/fails/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var variables = require('../../') 4 | 5 | var markdown = ` 6 | # {{ .title }} 7 | ` 8 | 9 | remark() 10 | .use(variables, { fail: true }) 11 | .process(markdown, function (err, file) { 12 | console.error(reporter(err || file)) 13 | console.log('vfile == null? %s', file == null) 14 | }) 15 | -------------------------------------------------------------------------------- /examples/with-rehype/output.md: -------------------------------------------------------------------------------- 1 | no issues found 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 |11 |13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/without-messages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "without-messages", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "remark": "^9.0.0", 14 | "vfile-reporter": "^5.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/with-messages/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var variables = require('../../') 4 | 5 | var markdown = ` 6 | # {{ title }} 7 | 8 | > {{ subtitle }} 9 | ` 10 | 11 | remark() 12 | .use(variables) 13 | .data('title', 'Example with messages') 14 | .process(markdown, function (err, file) { 15 | console.error(reporter(err || file)) 16 | console.log(String(file)) 17 | }) 18 | -------------------------------------------------------------------------------- /lib/visitor.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var test = require('./match') 3 | 4 | module.exports = visitor 5 | 6 | function visitor (fence) { 7 | return function (node) { 8 | var file = this.file 9 | var match 10 | var found 11 | 12 | match = test(node.value, fence) 13 | found = utils.hasData(match[1], file.data) 14 | 15 | if (found || found === 0) { 16 | return found 17 | } 18 | 19 | return node.value 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/without-messages/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var variables = require('../../') 4 | 5 | var markdown = ` 6 | # {{ .title }} 7 | 8 | > {{ .subtitle }} 9 | ` 10 | 11 | remark() 12 | .use(variables, { quiet: true }) 13 | .data('title', 'Example using quiet option') 14 | .process(markdown, function (err, file) { 15 | console.error(reporter(err || file)) 16 | console.log(String(file)) 17 | }) 18 | -------------------------------------------------------------------------------- /examples/correct-positions/output.md: -------------------------------------------------------------------------------- 1 | 2:1 warning Remove 1 line before node no-consecutive-blank-lines remark-lint 2 | 4:47-4:63 warning Strong should use `*` as a marker strong-marker remark-lint 3 | 5:7-5:15 warning Strong should use `*` as a marker strong-marker remark-lint 4 | 5 | ⚠ 3 warnings 6 | # Correct positional information 7 | 8 | > Should have correct positional information: **here**. 9 | > and **here**. 10 | 11 | -------------------------------------------------------------------------------- /examples/custom-markers/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var variables = require('../../') 4 | 5 | var markdown = ` 6 | # [.title] 7 | 8 | > [.subtitle[0]] 9 | ` 10 | 11 | remark() 12 | .use(variables, ['[', ']']) 13 | .data('title', 'Example with custom markers') 14 | .data('subtitle', ['here']) 15 | .process(markdown, function (err, file) { 16 | console.error(reporter(err || file)) 17 | console.log(String(file)) 18 | }) 19 | -------------------------------------------------------------------------------- /examples/basic/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var variables = require('../../') 4 | 5 | var markdown = ` 6 | # {{ .title }} 7 | 8 | - {{ .list[0] }} (string) 9 | - {{ .list[1] }} (number) 10 | - {{ .list[2] }} (boolean) 11 | ` 12 | 13 | remark() 14 | .use(variables) 15 | .data('title', 'Example') 16 | .data('list', ['Markdown', 0, false]) 17 | .process(markdown, function (err, file) { 18 | console.error(reporter(err || file)) 19 | console.log(String(file)) 20 | }) 21 | -------------------------------------------------------------------------------- /examples/from-vfiles-data/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var frontmatter = require('remark-frontmatter') 4 | var extract = require('remark-extract-frontmatter') 5 | var yaml = require('yaml').parse 6 | var variables = require('../../') 7 | 8 | var markdown = ` 9 | --- 10 | title: Example using vfile's data property 11 | --- 12 | 13 | {{ .title }} 14 | 15 | > {{ .subtitle }} 16 | ` 17 | 18 | remark() 19 | .use(frontmatter) 20 | .use(extract, yaml) 21 | .use(variables) 22 | .process(markdown, function (err, file) { 23 | console.error(reporter(err || file)) 24 | console.log(String(file)) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/correct-positions/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var reporter = require('vfile-reporter') 3 | var styleGuide = require('remark-preset-lint-markdown-style-guide') 4 | var variables = require('../../') 5 | 6 | var markdown = ` 7 | # {{ title }} 8 | 9 | > Should have correct positional information: __{{ marker }}__. 10 | > and {{ inline }}. 11 | ` 12 | 13 | remark() 14 | .use(styleGuide) 15 | .use(variables) 16 | .data('title', 'Correct positional information') 17 | .data('inline', '__here__') 18 | .data('marker', 'here') 19 | .process(markdown, function (err, file) { 20 | console.error(reporter(err || file)) 21 | console.log(file.toString()) 22 | }) 23 | -------------------------------------------------------------------------------- /examples/with-rehype/index.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var rehype = require('remark-rehype') 3 | var format = require('rehype-format') 4 | var document = require('rehype-document') 5 | var stringify = require('rehype-stringify') 6 | var reporter = require('vfile-reporter') 7 | var variables = require('../../') 8 | 9 | var markdown = ` 10 | # [.title] 11 | 12 | > Some bold text __[.foo]__ cool. 13 | ` 14 | 15 | remark() 16 | .use(variables, ['[', ']']) 17 | .use(rehype) 18 | .use(document) 19 | .use(format) 20 | .use(stringify) 21 | .data('title', 'Example bridging to rehype') 22 | .data('foo', 'here') 23 | .process(markdown, function (err, file) { 24 | console.error(reporter(err || file)) 25 | console.log(String(file)) 26 | }) 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-variables", 3 | "version": "1.4.9", 4 | "description": "Adds variables support to remark", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=8" 8 | }, 9 | "scripts": { 10 | "test": "tap -Rspec --100 test.js", 11 | "report": "nyc report --reporter=text-lcov | coveralls" 12 | }, 13 | "keywords": [ 14 | "remark", 15 | "plugin", 16 | "unified", 17 | "markdown", 18 | "template" 19 | ], 20 | "author": "Paul Zimmer", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "coveralls": "^3.1.0", 24 | "remark": "^11.0.0", 25 | "tap": "^15.0.6" 26 | }, 27 | "directories": { 28 | "example": "example" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/mrzmmr/remark-variables.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/mrzmmr/remark-variables/issues" 36 | }, 37 | "homepage": "https://github.com/mrzmmr/remark-variables#readme" 38 | } 39 | -------------------------------------------------------------------------------- /lib/match.js: -------------------------------------------------------------------------------- 1 | module.exports = match 2 | 3 | function match (value, fence) { 4 | var offset 5 | var start 6 | var close 7 | var open 8 | var same 9 | var left 10 | var nl 11 | 12 | if (!fence) { 13 | return 14 | } 15 | 16 | open = fence[0] 17 | close = fence[1] 18 | same = open === close 19 | start = value.indexOf(open) 20 | offset = value.indexOf(close, start) 21 | nl = '\n' 22 | 23 | if (start !== 0 || offset < 0) { 24 | return 25 | } 26 | 27 | index = start + open.length - 1 28 | left = 1 29 | 30 | while (left > 0 && value.charAt(index) !== nl) { 31 | index += 1 32 | 33 | if (value.slice(index, index + open.length) === open && !same) { 34 | left += 1 35 | } 36 | 37 | if (value.slice(index, index + close.length) !== close) { 38 | continue 39 | } 40 | 41 | left -= 1 42 | 43 | if (left < 1) { 44 | return [ 45 | value.slice(start, index + close.length), 46 | value.slice(start + open.length, index).trim() 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paul Zimmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # vim swap files 64 | *.sw[a-p] 65 | 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var tokenizer = require('./lib/tokenizer') 2 | var visitor = require('./lib/visitor') 3 | var utils = require('./lib/utils') 4 | 5 | module.exports = variables 6 | 7 | function variables (options) { 8 | options = utils.settings(options) 9 | 10 | var self = this 11 | var parser = self.Parser 12 | var compiler = self.Compiler 13 | var data = self.data() 14 | 15 | var fail = options.fail 16 | var quiet = options.quiet 17 | var fence = options.fence 18 | var opening = fence[0] 19 | var name = options.name 20 | var test = options.test 21 | 22 | if (isParser(parser)) { 23 | attatchParser( 24 | name, 25 | parser, 26 | tokenizer(name, data, fence, quiet, fail), 27 | locator(opening) 28 | ) 29 | } 30 | 31 | if (isCompiler(compiler)) { 32 | attatchCompiler(name, compiler, visitor(fence)) 33 | } 34 | } 35 | 36 | function attatchParser (name, parser, tokenizer, locator) { 37 | var proto = parser.prototype 38 | var tokenizers = proto.inlineTokenizers 39 | var methods = proto.inlineMethods 40 | 41 | tokenizer.locator = locator 42 | tokenizers[name] = tokenizer 43 | methods.splice(methods.indexOf('link'), 0, name) 44 | } 45 | 46 | function attatchCompiler (name, compiler, visitor) { 47 | compiler.prototype.visitors[name] = visitor 48 | } 49 | 50 | function locator (opening) { 51 | return function (value, fromIndex) { 52 | return value.indexOf(opening, fromIndex) 53 | } 54 | } 55 | 56 | function isParser (parser) { 57 | return Boolean(parser && parser.prototype && parser.prototype.inlineTokenizers) 58 | } 59 | 60 | function isCompiler (compiler) { 61 | return Boolean(compiler && compiler.prototype && compiler.prototype.visitors) 62 | } 63 | -------------------------------------------------------------------------------- /lib/tokenizer.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var test = require('./match') 3 | 4 | module.exports = tokenizer 5 | 6 | function tokenizer (name, data, fence, quiet, fail) { 7 | return function (eat, value, silent) { 8 | var tokenized 9 | var fromFile 10 | var fromData 11 | var subvalue 12 | var message 13 | var found 14 | var match 15 | var file 16 | var self 17 | var node 18 | var add 19 | var val 20 | var now 21 | var i 22 | 23 | self = this 24 | file = self.file 25 | match = test(value, fence) 26 | 27 | if (match) { 28 | subvalue = match[0] 29 | sub = match[1].trim() 30 | 31 | fromFile = utils.hasData(sub, file.data) 32 | fromData = utils.hasData(sub, data) 33 | found = fromFile || fromData 34 | now = eat.now() 35 | 36 | /* istanbul ignore if */ 37 | if (silent) { 38 | return true 39 | } 40 | 41 | add = eat(subvalue) 42 | 43 | if (found != null) { 44 | tokenized = self.tokenizeInline(found.toString(), now) 45 | i = -1 46 | 47 | while (++i < tokenized.length) { 48 | node = add(tokenized[i]) 49 | } 50 | 51 | return node 52 | } 53 | 54 | if (!found && !quiet) { 55 | sub = sub.indexOf('.') === 0 ? sub : '.' + sub 56 | message = 'Could not resolve `data' + sub + '` in VFile or Processor.' 57 | 58 | if (fail) { 59 | return file.fail(message, now, 'variables:undef-variable') 60 | } else { 61 | file.message(message, now, 'variables:undef-variable') 62 | } 63 | } 64 | 65 | return add({ 66 | type: name, 67 | value: subvalue 68 | }) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | exports.settings = settings 2 | exports.hasData = hasData 3 | 4 | function settings (options) { 5 | var returns 6 | 7 | returns = {} 8 | options = options || {} 9 | returns.fence = fences(options) 10 | returns.fail = is(options.fail, 'Boolean') ? options.fail : false 11 | returns.quiet = is(options.quiet, 'Boolean') ? options.quiet : false 12 | returns.name = is(options.name, 'String') ? options.name : 'variables' 13 | return returns 14 | } 15 | 16 | function fences (value) { 17 | var defaults = [ '{{', '}}' ] 18 | 19 | if (value && value.fence) { 20 | return fences(value.fence) 21 | } 22 | 23 | if (is(value, 'Array') && value.length) { 24 | var open = is(value[0], 'String') ? value[0] : defaults[0] 25 | var closed 26 | 27 | if (open === defaults[0]) { 28 | closed = defaults[1] 29 | } else { 30 | closed = is(value[1], 'String') ? value[1] : value[0] 31 | } 32 | 33 | return [ open, closed ] 34 | } 35 | if (is(value, 'String')) { 36 | return [ value, value ] 37 | } 38 | if (is(value, 'Object') && value.open) { 39 | return [ value.open, value.close || value.open ] 40 | } 41 | 42 | return defaults 43 | } 44 | 45 | function is (value, expected) { 46 | var toString = Object.prototype.toString 47 | var match = toString.call(value).match(/\[object(.*?)\]/) 48 | 49 | /* istanbul ignore else */ 50 | if (match) { 51 | actual = match[1].trim() 52 | return actual === expected 53 | } else { 54 | return false 55 | } 56 | } 57 | 58 | function hasData (string, data) { 59 | var splitter = /\.|\[(\d+)\]/ 60 | var value 61 | var keys 62 | var i 63 | 64 | function empty (value) { 65 | return value && value.length 66 | } 67 | 68 | i = -1 69 | value = data 70 | string = string.trim() 71 | keys = string.split(splitter).filter(empty) 72 | 73 | while (++i < keys.length) { 74 | val = value[keys[i]] 75 | if (val != null) { 76 | value = val 77 | continue 78 | } else { 79 | return null 80 | } 81 | } 82 | 83 | return value 84 | } 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # remark-variables 2 | 3 | [](https://travis-ci.org/mrzmmr/remark-variables) 4 | [](https://coveralls.io/github/mrzmmr/remark-variables?branch=master) 6 | 7 | This plugin adds variables support to [remark](https://github.com/remarkjs/remark) using a customizable template syntax. Variables are checked against both the [vfile](https://github.com/vfile/vfile#vfiledata)'s, and [processor](https://github.com/unifiedjs/unified#processordatakey-value)'s **[data](https://github.com/vfile/vfile#vfiledata)** property and can use both dot and bracket syntax. For example, `foo.bar[0]` resolve to `vfile.data.foo.bar[0]` if it exists, or do nothing if the property does not exist. By default, this plugin uses double curly braces `{{`, `}}` to denote variables but can be configured. 8 | 9 | ## Install 10 | 11 | ``` 12 | npm install --save remark-variables 13 | ``` 14 | 15 | ## Usage 16 | 17 | If we have a file, example.md 18 | 19 | ``` 20 | # {{ title }} 21 | 22 | - {{ list[0] }} (string) 23 | - {{ list[1] }} (number) 24 | - {{ list[2] }} (boolean) 25 | 26 | > {{ subtitle }} 27 | ``` 28 | 29 | and 30 | 31 | ```js 32 | var unified = require('unified') 33 | var parser = require('remark-parse') 34 | var compiler = require('remark-stringify') 35 | var variables = require('remark-variables') 36 | var reporter = require('vfile-reporter') 37 | var toVfile = require('to-vfile') 38 | 39 | var markdown = toVfile('./example.md') 40 | 41 | // Set the processor 42 | var processor = unified() 43 | .use(parser) 44 | .use(compiler) 45 | .use(variables) 46 | 47 | // Add some data 48 | processor = processor() 49 | .data('title', 'Example') 50 | .data('subtitle', 'Variables in markdown!') 51 | .data('list', [ 'other text', 0, true ]) 52 | 53 | // And process 54 | processor().process(markdown, function (err, file) { 55 | console.error(reporter(err || file)) 56 | console.log(file.toString()) 57 | }) 58 | ``` 59 | 60 | the output would be 61 | 62 | ``` 63 | ./example.md: no issues found 64 | # Example 65 | 66 | - other text (string) 67 | - 0 (number) 68 | - true (boolean) 69 | 70 | > Variables in markdown! 71 | ``` 72 | 73 | ### Options 74 | 75 | Type: `Object` | `Array` | `String` 76 | 77 | If options is a string, then it is used as the opening and closing fence markers, for example: `.use(variables, ':')` would match `:some.variable[0]:` look for `vfile.data.some.variable[0]`. If options is an array, options[0] and options[1] are used as fence markers. 78 | 79 | #### fence 80 | 81 | Type: `Array` 82 | 83 | Default: [ '{{', '}}' ] 84 | 85 | Markers used to denote a variable to be replaced. 86 | 87 | ## License 88 | 89 | MIT © Paul Zimmer 90 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var remark = require('remark') 2 | var test = require('tap').test 3 | var variables = require('.') 4 | var match = require('./lib/match') 5 | 6 | test('variables', function (t) { 7 | t.doesNotThrow(function () { 8 | remark() 9 | .use(variables) 10 | .freeze() 11 | }, 'should not throw without parser') 12 | 13 | t.doesNotThrow(function () { 14 | t.ok(remark().use(variables)) 15 | }, 'should not throw without options') 16 | 17 | t.doesNotThrow(function() { 18 | var file = remark() 19 | .use(variables) 20 | .processSync('{{ foo }}') 21 | 22 | t.ok(file.toString() === '{{ foo }}\n') 23 | }, 'should not throw when no data is found') 24 | 25 | t.doesNotThrow(function () { 26 | var file = remark() 27 | .use(variables) 28 | .data('foo', 'bar') 29 | .processSync('{{ foo }}') 30 | t.ok(file.toString() === 'bar\n') 31 | }, 'should not throw with default options') 32 | 33 | t.doesNotThrow(function () { 34 | var file = remark() 35 | .use(variables, ':') 36 | .data('foo', 'bar') 37 | .processSync(': foo :') 38 | t.ok(file.toString() === 'bar\n') 39 | }, 'should not throw with custom fence as string') 40 | 41 | t.doesNotThrow(function () { 42 | var file = remark() 43 | .use(variables, [':']) 44 | .data('foo', 'bar') 45 | .processSync(': foo :') 46 | 47 | t.ok(file.toString() === 'bar\n') 48 | }, 'should not throw with custom fence as an array.length = 1') 49 | 50 | t.doesNotThrow(function () { 51 | var file = remark() 52 | .use(variables, ['->', '<-']) 53 | .data('foo', 'bar') 54 | .processSync('-> foo <-') 55 | 56 | t.ok(file.toString() === 'bar\n') 57 | }, 'should not throw with custom fence as an array.length = 2') 58 | 59 | t.doesNotThrow(function () { 60 | var file = remark() 61 | .use(variables, [false]) 62 | .processSync('foo') 63 | 64 | t.ok(file.toString() === 'foo\n') 65 | }, 'should not throw and should resort to defaults with bad custom fence') 66 | 67 | t.doesNotThrow(function () { 68 | var file = remark() 69 | .use(variables, { fence: ':' }) 70 | .data('foo', 'bar') 71 | .processSync(': foo :') 72 | 73 | t.ok(file.toString() === 'bar\n') 74 | }, 'should not throw with custom fence as an object') 75 | 76 | t.doesNotThrow(function () { 77 | var file = remark() 78 | .use(variables, { fence: { open: '{{', close: '}}' } }) 79 | .data('foo', 'bar') 80 | .processSync('{{ foo }}') 81 | 82 | t.ok(file.toString() === 'bar\n') 83 | }, 'should not throw with custom fence as an object; open, close') 84 | 85 | t.doesNotThrow(function () { 86 | var file = remark() 87 | .use(variables, { fence: { open: ':' } }) 88 | .data('foo', 'bar') 89 | .processSync(': foo :') 90 | 91 | t.ok(file.toString() == 'bar\n') 92 | }, 'should not throw with custom fence as an object; open') 93 | 94 | t.doesNotThrow(function () { 95 | var file = remark() 96 | .use(variables, { name: 'myVariables' }) 97 | .data('foo', 'bar') 98 | .processSync('{{ foo }}') 99 | 100 | t.ok(file.toString() === 'bar\n') 101 | }, 'should not throw with custom node type name') 102 | 103 | t.doesNotThrow(function () { 104 | var file = remark() 105 | .use(variables) 106 | .use(function () { 107 | return function (tree, file) { 108 | file.data.foo = 'bar' 109 | } 110 | }) 111 | .processSync('{{ foo }}') 112 | 113 | t.ok(file.toString() === 'bar\n') 114 | }, 'should not throw with data from transform stage') 115 | 116 | t.doesNotThrow(function () { 117 | var file = remark() 118 | .use(variables) 119 | .data('foo', 0) 120 | .processSync('{{ .foo }}') 121 | 122 | t.ok(file.toString() === '0\n') 123 | 124 | var file = remark() 125 | .use(variables) 126 | .data('foo', false) 127 | .processSync('{{ foo }}') 128 | 129 | t.ok(file.toString() === 'false\n') 130 | }, 'should not throw and match when data value is zero or false') 131 | 132 | t.throws(function () { 133 | remark() 134 | .use(variables, { fail: true }) 135 | .processSync('{{ foo }}') 136 | }) 137 | 138 | t.doesNotThrow(function () { 139 | var file = remark() 140 | .use(variables) 141 | .processSync('{{ .foo }}') 142 | 143 | t.ok(file.messages[0].message === 'Could not resolve `data' + sub + '` in VFile or Processor.') 144 | }, 'should not throw and should create message') 145 | 146 | t.doesNotThrow(function () { 147 | var file = remark() 148 | .use(variables, { quiet: true }) 149 | .processSync('{{ .foo }}') 150 | 151 | t.ok(file.messages.length === 0) 152 | }, 'should not throw and no messages with quiet option') 153 | 154 | t.doesNotThrow(function () { 155 | remark() 156 | .use(function () { 157 | this.Parser = function () { 158 | return { 159 | type: 'root', 160 | children: [] 161 | } 162 | } 163 | }) 164 | .use(variables) 165 | .processSync('{{ .foo }}') 166 | }, 'should do nothing if is not a remark parser.') 167 | 168 | t.doesNotThrow(function () { 169 | remark() 170 | .use(function () { 171 | this.Compiler = function () { 172 | return '' 173 | } 174 | }) 175 | .use(variables) 176 | .processSync('{{ .foo }}') 177 | }, 'should do nothing if is not a remark compiler.') 178 | 179 | t.end() 180 | }) 181 | 182 | 183 | test('Match function', t => { 184 | t.doesNotThrow(() => { 185 | t.ok(match('') == null, 'should return undefined with no fence.') 186 | t.ok(match('[foo]', [':', ':']) == null, 'should return undefined with no match.') 187 | t.ok(match('[ foo [', ['[', ']']) == null, 'should return undefined with no closing match.') 188 | t.deepEqual(match('[foo]', ['[', ']']), ['[foo]', 'foo'], 'should match fence.') 189 | t.deepEqual(match(': foo :', [':', ':']), [': foo :', 'foo'], 'should match same markers fence.') 190 | t.deepEqual( 191 | match('[ foo ] [bar]', ['[', ']']), 192 | ['[ foo ]', 'foo'], 193 | 'should match first occurence only.' 194 | ) 195 | t.deepEqual( 196 | match('[ foo[0] ]', ['[', ']']), 197 | ['[ foo[0] ]', 'foo[0]'], 198 | 'should match outer fence.' 199 | ) 200 | }) 201 | t.end() 202 | }) 203 | 204 | --------------------------------------------------------------------------------Some bold text here.
12 |