/** * class Ruler * * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and * [[MarkdownIt#inline]] to manage sequences of functions (rules): * * - keep rules in defined order * - assign the name to each rule * - enable/disable rules * - add/replace rules * - allow assign rules to additional named chains (in the same) * - cacheing lists of active rules * * You will not need use this class directly until write plugins. For simple * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and * [[MarkdownIt.use]]. **/ /** * new Ruler() **/ function Ruler () { // List of added rules. Each element is: // // { // name: XXX, // enabled: Boolean, // fn: Function(), // alt: [ name2, name3 ] // } // this.__rules__ = [] // Cached rule chains. // // First level - chain name, '' for default. // Second level - diginal anchor for fast filtering by charcodes. // this.__cache__ = null } // Helper methods, should not be used directly // Find rule index by name // Ruler.prototype.__find__ = function (name) { for (let i = 0; i < this.__rules__.length; i++) { if (this.__rules__[i].name === name) { return i } } return -1 } // Build rules lookup cache // Ruler.prototype.__compile__ = function () { const self = this const chains = [''] // collect unique names self.__rules__.forEach(function (rule) { if (!rule.enabled) { return } rule.alt.forEach(function (altName) { if (chains.indexOf(altName) < 0) { chains.push(altName) } }) }) self.__cache__ = {} chains.forEach(function (chain) { self.__cache__[chain] = [] self.__rules__.forEach(function (rule) { if (!rule.enabled) { return } if (chain && rule.alt.indexOf(chain) < 0) { return } self.__cache__[chain].push(rule.fn) }) }) } /** * Ruler.at(name, fn [, options]) * - name (String): rule name to replace. * - fn (Function): new rule function. * - options (Object): new rule options (not mandatory). * * Replace rule by name with new function & options. Throws error if name not * found. * * ##### Options: * * - __alt__ - array with names of "alternate" chains. * * ##### Example * * Replace existing typographer replacement rule with new one: * * ```javascript * var md = require('markdown-it')(); * * md.core.ruler.at('replacements', function replace(state) { * //... * }); * ``` **/ Ruler.prototype.at = function (name, fn, options) { const index = this.__find__(name) const opt = options || {} if (index === -1) { throw new Error('Parser rule not found: ' + name) } this.__rules__[index].fn = fn this.__rules__[index].alt = opt.alt || [] this.__cache__ = null } /** * Ruler.before(beforeName, ruleName, fn [, options]) * - beforeName (String): new rule will be added before this one. * - ruleName (String): name of added rule. * - fn (Function): rule function. * - options (Object): rule options (not mandatory). * * Add new rule to chain before one with given name. See also * [[Ruler.after]], [[Ruler.push]]. * * ##### Options: * * - __alt__ - array with names of "alternate" chains. * * ##### Example * * ```javascript * var md = require('markdown-it')(); * * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { * //... * }); * ``` **/ Ruler.prototype.before = function (beforeName, ruleName, fn, options) { const index = this.__find__(beforeName) const opt = options || {} if (index === -1) { throw new Error('Parser rule not found: ' + beforeName) } this.__rules__.splice(index, 0, { name: ruleName, enabled: true, fn: fn, alt: opt.alt || [] }) this.__cache__ = null } /** * Ruler.after(afterName, ruleName, fn [, options]) * - afterName (String): new rule will be added after this one. * - ruleName (String): name of added rule. * - fn (Function): rule function. * - options (Object): rule options (not mandatory). * * Add new rule to chain after one with given name. See also * [[Ruler.before]], [[Ruler.push]]. * * ##### Options: * * - __alt__ - array with names of "alternate" chains. * * ##### Example * * ```javascript * var md = require('markdown-it')(); * * md.inline.ruler.after('text', 'my_rule', function replace(state) { * //... * }); * ``` **/ Ruler.prototype.after = function (afterName, ruleName, fn, options) { const index = this.__find__(afterName) const opt = options || {} if (index === -1) { throw new Error('Parser rule not found: ' + afterName) } this.__rules__.splice(index + 1, 0, { name: ruleName, enabled: true, fn: fn, alt: opt.alt || [] }) this.__cache__ = null } /** * Ruler.push(ruleName, fn [, options]) * - ruleName (String): name of added rule. * - fn (Function): rule function. * - options (Object): rule options (not mandatory). * * Push new rule to the end of chain. See also * [[Ruler.before]], [[Ruler.after]]. * * ##### Options: * * - __alt__ - array with names of "alternate" chains. * * ##### Example * * ```javascript * var md = require('markdown-it')(); * * md.core.ruler.push('my_rule', function replace(state) { * //... * }); * ``` **/ Ruler.prototype.push = function (ruleName, fn, options) { const opt = options || {} this.__rules__.push({ name: ruleName, enabled: true, fn: fn, alt: opt.alt || [] }) this.__cache__ = null } /** * Ruler.enable(list [, ignoreInvalid]) -> Array * - list (String|Array): list of rule names to enable. * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. * * Enable rules with given names. If any rule name not found - throw Error. * Errors can be disabled by second param. * * Returns list of found rule names (if no exception happened). * * See also [[Ruler.disable]], [[Ruler.enableOnly]]. **/ Ruler.prototype.enable = function (list, ignoreInvalid) { if (!Array.isArray(list)) { list = [list] } const result = [] // Search by name and enable list.forEach(function (name) { const idx = this.__find__(name) if (idx < 0) { if (ignoreInvalid) { return } throw new Error('Rules manager: invalid rule name ' + name) } this.__rules__[idx].enabled = true result.push(name) }, this) this.__cache__ = null return result } /** * Ruler.enableOnly(list [, ignoreInvalid]) * - list (String|Array): list of rule names to enable (whitelist). * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. * * Enable rules with given names, and disable everything else. If any rule name * not found - throw Error. Errors can be disabled by second param. * * See also [[Ruler.disable]], [[Ruler.enable]]. **/ Ruler.prototype.enableOnly = function (list, ignoreInvalid) { if (!Array.isArray(list)) { list = [list] } this.__rules__.forEach(function (rule) { rule.enabled = false }) this.enable(list, ignoreInvalid) } /** * Ruler.disable(list [, ignoreInvalid]) -> Array * - list (String|Array): list of rule names to disable. * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. * * Disable rules with given names. If any rule name not found - throw Error. * Errors can be disabled by second param. * * Returns list of found rule names (if no exception happened). * * See also [[Ruler.enable]], [[Ruler.enableOnly]]. **/ Ruler.prototype.disable = function (list, ignoreInvalid) { if (!Array.isArray(list)) { list = [list] } const result = [] // Search by name and disable list.forEach(function (name) { const idx = this.__find__(name) if (idx < 0) { if (ignoreInvalid) { return } throw new Error('Rules manager: invalid rule name ' + name) } this.__rules__[idx].enabled = false result.push(name) }, this) this.__cache__ = null return result } /** * Ruler.getRules(chainName) -> Array * * Return array of active functions (rules) for given chain name. It analyzes * rules configuration, compiles caches if not exists and returns result. * * Default chain name is `''` (empty string). It can't be skipped. That's * done intentionally, to keep signature monomorphic for high speed. **/ Ruler.prototype.getRules = function (chainName) { if (this.__cache__ === null) { this.__compile__() } // Chain can be empty, if rules disabled. But we still have to return Array. return this.__cache__[chainName] || [] } export default Ruler