ruler.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /**
  2. * class Ruler
  3. *
  4. * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and
  5. * [[MarkdownIt#inline]] to manage sequences of functions (rules):
  6. *
  7. * - keep rules in defined order
  8. * - assign the name to each rule
  9. * - enable/disable rules
  10. * - add/replace rules
  11. * - allow assign rules to additional named chains (in the same)
  12. * - cacheing lists of active rules
  13. *
  14. * You will not need use this class directly until write plugins. For simple
  15. * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and
  16. * [[MarkdownIt.use]].
  17. **/
  18. 'use strict';
  19. /**
  20. * new Ruler()
  21. **/
  22. function Ruler() {
  23. // List of added rules. Each element is:
  24. //
  25. // {
  26. // name: XXX,
  27. // enabled: Boolean,
  28. // fn: Function(),
  29. // alt: [ name2, name3 ]
  30. // }
  31. //
  32. this.__rules__ = [];
  33. // Cached rule chains.
  34. //
  35. // First level - chain name, '' for default.
  36. // Second level - diginal anchor for fast filtering by charcodes.
  37. //
  38. this.__cache__ = null;
  39. }
  40. ////////////////////////////////////////////////////////////////////////////////
  41. // Helper methods, should not be used directly
  42. // Find rule index by name
  43. //
  44. Ruler.prototype.__find__ = function (name) {
  45. for (var i = 0; i < this.__rules__.length; i++) {
  46. if (this.__rules__[i].name === name) {
  47. return i;
  48. }
  49. }
  50. return -1;
  51. };
  52. // Build rules lookup cache
  53. //
  54. Ruler.prototype.__compile__ = function () {
  55. var self = this;
  56. var chains = [ '' ];
  57. // collect unique names
  58. self.__rules__.forEach(function (rule) {
  59. if (!rule.enabled) { return; }
  60. rule.alt.forEach(function (altName) {
  61. if (chains.indexOf(altName) < 0) {
  62. chains.push(altName);
  63. }
  64. });
  65. });
  66. self.__cache__ = {};
  67. chains.forEach(function (chain) {
  68. self.__cache__[chain] = [];
  69. self.__rules__.forEach(function (rule) {
  70. if (!rule.enabled) { return; }
  71. if (chain && rule.alt.indexOf(chain) < 0) { return; }
  72. self.__cache__[chain].push(rule.fn);
  73. });
  74. });
  75. };
  76. /**
  77. * Ruler.at(name, fn [, options])
  78. * - name (String): rule name to replace.
  79. * - fn (Function): new rule function.
  80. * - options (Object): new rule options (not mandatory).
  81. *
  82. * Replace rule by name with new function & options. Throws error if name not
  83. * found.
  84. *
  85. * ##### Options:
  86. *
  87. * - __alt__ - array with names of "alternate" chains.
  88. *
  89. * ##### Example
  90. *
  91. * Replace existing typographer replacement rule with new one:
  92. *
  93. * ```javascript
  94. * var md = require('markdown-it')();
  95. *
  96. * md.core.ruler.at('replacements', function replace(state) {
  97. * //...
  98. * });
  99. * ```
  100. **/
  101. Ruler.prototype.at = function (name, fn, options) {
  102. var index = this.__find__(name);
  103. var opt = options || {};
  104. if (index === -1) { throw new Error('Parser rule not found: ' + name); }
  105. this.__rules__[index].fn = fn;
  106. this.__rules__[index].alt = opt.alt || [];
  107. this.__cache__ = null;
  108. };
  109. /**
  110. * Ruler.before(beforeName, ruleName, fn [, options])
  111. * - beforeName (String): new rule will be added before this one.
  112. * - ruleName (String): name of added rule.
  113. * - fn (Function): rule function.
  114. * - options (Object): rule options (not mandatory).
  115. *
  116. * Add new rule to chain before one with given name. See also
  117. * [[Ruler.after]], [[Ruler.push]].
  118. *
  119. * ##### Options:
  120. *
  121. * - __alt__ - array with names of "alternate" chains.
  122. *
  123. * ##### Example
  124. *
  125. * ```javascript
  126. * var md = require('markdown-it')();
  127. *
  128. * md.block.ruler.before('paragraph', 'my_rule', function replace(state) {
  129. * //...
  130. * });
  131. * ```
  132. **/
  133. Ruler.prototype.before = function (beforeName, ruleName, fn, options) {
  134. var index = this.__find__(beforeName);
  135. var opt = options || {};
  136. if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); }
  137. this.__rules__.splice(index, 0, {
  138. name: ruleName,
  139. enabled: true,
  140. fn: fn,
  141. alt: opt.alt || []
  142. });
  143. this.__cache__ = null;
  144. };
  145. /**
  146. * Ruler.after(afterName, ruleName, fn [, options])
  147. * - afterName (String): new rule will be added after this one.
  148. * - ruleName (String): name of added rule.
  149. * - fn (Function): rule function.
  150. * - options (Object): rule options (not mandatory).
  151. *
  152. * Add new rule to chain after one with given name. See also
  153. * [[Ruler.before]], [[Ruler.push]].
  154. *
  155. * ##### Options:
  156. *
  157. * - __alt__ - array with names of "alternate" chains.
  158. *
  159. * ##### Example
  160. *
  161. * ```javascript
  162. * var md = require('markdown-it')();
  163. *
  164. * md.inline.ruler.after('text', 'my_rule', function replace(state) {
  165. * //...
  166. * });
  167. * ```
  168. **/
  169. Ruler.prototype.after = function (afterName, ruleName, fn, options) {
  170. var index = this.__find__(afterName);
  171. var opt = options || {};
  172. if (index === -1) { throw new Error('Parser rule not found: ' + afterName); }
  173. this.__rules__.splice(index + 1, 0, {
  174. name: ruleName,
  175. enabled: true,
  176. fn: fn,
  177. alt: opt.alt || []
  178. });
  179. this.__cache__ = null;
  180. };
  181. /**
  182. * Ruler.push(ruleName, fn [, options])
  183. * - ruleName (String): name of added rule.
  184. * - fn (Function): rule function.
  185. * - options (Object): rule options (not mandatory).
  186. *
  187. * Push new rule to the end of chain. See also
  188. * [[Ruler.before]], [[Ruler.after]].
  189. *
  190. * ##### Options:
  191. *
  192. * - __alt__ - array with names of "alternate" chains.
  193. *
  194. * ##### Example
  195. *
  196. * ```javascript
  197. * var md = require('markdown-it')();
  198. *
  199. * md.core.ruler.push('my_rule', function replace(state) {
  200. * //...
  201. * });
  202. * ```
  203. **/
  204. Ruler.prototype.push = function (ruleName, fn, options) {
  205. var opt = options || {};
  206. this.__rules__.push({
  207. name: ruleName,
  208. enabled: true,
  209. fn: fn,
  210. alt: opt.alt || []
  211. });
  212. this.__cache__ = null;
  213. };
  214. /**
  215. * Ruler.enable(list [, ignoreInvalid]) -> Array
  216. * - list (String|Array): list of rule names to enable.
  217. * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.
  218. *
  219. * Enable rules with given names. If any rule name not found - throw Error.
  220. * Errors can be disabled by second param.
  221. *
  222. * Returns list of found rule names (if no exception happened).
  223. *
  224. * See also [[Ruler.disable]], [[Ruler.enableOnly]].
  225. **/
  226. Ruler.prototype.enable = function (list, ignoreInvalid) {
  227. if (!Array.isArray(list)) { list = [ list ]; }
  228. var result = [];
  229. // Search by name and enable
  230. list.forEach(function (name) {
  231. var idx = this.__find__(name);
  232. if (idx < 0) {
  233. if (ignoreInvalid) { return; }
  234. throw new Error('Rules manager: invalid rule name ' + name);
  235. }
  236. this.__rules__[idx].enabled = true;
  237. result.push(name);
  238. }, this);
  239. this.__cache__ = null;
  240. return result;
  241. };
  242. /**
  243. * Ruler.enableOnly(list [, ignoreInvalid])
  244. * - list (String|Array): list of rule names to enable (whitelist).
  245. * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.
  246. *
  247. * Enable rules with given names, and disable everything else. If any rule name
  248. * not found - throw Error. Errors can be disabled by second param.
  249. *
  250. * See also [[Ruler.disable]], [[Ruler.enable]].
  251. **/
  252. Ruler.prototype.enableOnly = function (list, ignoreInvalid) {
  253. if (!Array.isArray(list)) { list = [ list ]; }
  254. this.__rules__.forEach(function (rule) { rule.enabled = false; });
  255. this.enable(list, ignoreInvalid);
  256. };
  257. /**
  258. * Ruler.disable(list [, ignoreInvalid]) -> Array
  259. * - list (String|Array): list of rule names to disable.
  260. * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.
  261. *
  262. * Disable rules with given names. If any rule name not found - throw Error.
  263. * Errors can be disabled by second param.
  264. *
  265. * Returns list of found rule names (if no exception happened).
  266. *
  267. * See also [[Ruler.enable]], [[Ruler.enableOnly]].
  268. **/
  269. Ruler.prototype.disable = function (list, ignoreInvalid) {
  270. if (!Array.isArray(list)) { list = [ list ]; }
  271. var result = [];
  272. // Search by name and disable
  273. list.forEach(function (name) {
  274. var idx = this.__find__(name);
  275. if (idx < 0) {
  276. if (ignoreInvalid) { return; }
  277. throw new Error('Rules manager: invalid rule name ' + name);
  278. }
  279. this.__rules__[idx].enabled = false;
  280. result.push(name);
  281. }, this);
  282. this.__cache__ = null;
  283. return result;
  284. };
  285. /**
  286. * Ruler.getRules(chainName) -> Array
  287. *
  288. * Return array of active functions (rules) for given chain name. It analyzes
  289. * rules configuration, compiles caches if not exists and returns result.
  290. *
  291. * Default chain name is `''` (empty string). It can't be skipped. That's
  292. * done intentionally, to keep signature monomorphic for high speed.
  293. **/
  294. Ruler.prototype.getRules = function (chainName) {
  295. if (this.__cache__ === null) {
  296. this.__compile__();
  297. }
  298. // Chain can be empty, if rules disabled. But we still have to return Array.
  299. return this.__cache__[chainName] || [];
  300. };
  301. module.exports = Ruler;