index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. 'use strict'
  2. var bail = require('bail')
  3. var buffer = require('is-buffer')
  4. var extend = require('extend')
  5. var plain = require('is-plain-obj')
  6. var trough = require('trough')
  7. var vfile = require('vfile')
  8. // Expose a frozen processor.
  9. module.exports = unified().freeze()
  10. var slice = [].slice
  11. var own = {}.hasOwnProperty
  12. // Process pipeline.
  13. var pipeline = trough()
  14. .use(pipelineParse)
  15. .use(pipelineRun)
  16. .use(pipelineStringify)
  17. function pipelineParse(p, ctx) {
  18. ctx.tree = p.parse(ctx.file)
  19. }
  20. function pipelineRun(p, ctx, next) {
  21. p.run(ctx.tree, ctx.file, done)
  22. function done(error, tree, file) {
  23. if (error) {
  24. next(error)
  25. } else {
  26. ctx.tree = tree
  27. ctx.file = file
  28. next()
  29. }
  30. }
  31. }
  32. function pipelineStringify(p, ctx) {
  33. var result = p.stringify(ctx.tree, ctx.file)
  34. if (result === undefined || result === null) {
  35. // Empty.
  36. } else if (typeof result === 'string' || buffer(result)) {
  37. if ('value' in ctx.file) {
  38. ctx.file.value = result
  39. }
  40. ctx.file.contents = result
  41. } else {
  42. ctx.file.result = result
  43. }
  44. }
  45. // Function to create the first processor.
  46. function unified() {
  47. var attachers = []
  48. var transformers = trough()
  49. var namespace = {}
  50. var freezeIndex = -1
  51. var frozen
  52. // Data management.
  53. processor.data = data
  54. // Lock.
  55. processor.freeze = freeze
  56. // Plugins.
  57. processor.attachers = attachers
  58. processor.use = use
  59. // API.
  60. processor.parse = parse
  61. processor.stringify = stringify
  62. processor.run = run
  63. processor.runSync = runSync
  64. processor.process = process
  65. processor.processSync = processSync
  66. // Expose.
  67. return processor
  68. // Create a new processor based on the processor in the current scope.
  69. function processor() {
  70. var destination = unified()
  71. var index = -1
  72. while (++index < attachers.length) {
  73. destination.use.apply(null, attachers[index])
  74. }
  75. destination.data(extend(true, {}, namespace))
  76. return destination
  77. }
  78. // Freeze: used to signal a processor that has finished configuration.
  79. //
  80. // For example, take unified itself: it’s frozen.
  81. // Plugins should not be added to it.
  82. // Rather, it should be extended, by invoking it, before modifying it.
  83. //
  84. // In essence, always invoke this when exporting a processor.
  85. function freeze() {
  86. var values
  87. var transformer
  88. if (frozen) {
  89. return processor
  90. }
  91. while (++freezeIndex < attachers.length) {
  92. values = attachers[freezeIndex]
  93. if (values[1] === false) {
  94. continue
  95. }
  96. if (values[1] === true) {
  97. values[1] = undefined
  98. }
  99. transformer = values[0].apply(processor, values.slice(1))
  100. if (typeof transformer === 'function') {
  101. transformers.use(transformer)
  102. }
  103. }
  104. frozen = true
  105. freezeIndex = Infinity
  106. return processor
  107. }
  108. // Data management.
  109. // Getter / setter for processor-specific informtion.
  110. function data(key, value) {
  111. if (typeof key === 'string') {
  112. // Set `key`.
  113. if (arguments.length === 2) {
  114. assertUnfrozen('data', frozen)
  115. namespace[key] = value
  116. return processor
  117. }
  118. // Get `key`.
  119. return (own.call(namespace, key) && namespace[key]) || null
  120. }
  121. // Set space.
  122. if (key) {
  123. assertUnfrozen('data', frozen)
  124. namespace = key
  125. return processor
  126. }
  127. // Get space.
  128. return namespace
  129. }
  130. // Plugin management.
  131. //
  132. // Pass it:
  133. // * an attacher and options,
  134. // * a preset,
  135. // * a list of presets, attachers, and arguments (list of attachers and
  136. // options).
  137. function use(value) {
  138. var settings
  139. assertUnfrozen('use', frozen)
  140. if (value === null || value === undefined) {
  141. // Empty.
  142. } else if (typeof value === 'function') {
  143. addPlugin.apply(null, arguments)
  144. } else if (typeof value === 'object') {
  145. if ('length' in value) {
  146. addList(value)
  147. } else {
  148. addPreset(value)
  149. }
  150. } else {
  151. throw new Error('Expected usable value, not `' + value + '`')
  152. }
  153. if (settings) {
  154. namespace.settings = extend(namespace.settings || {}, settings)
  155. }
  156. return processor
  157. function addPreset(result) {
  158. addList(result.plugins)
  159. if (result.settings) {
  160. settings = extend(settings || {}, result.settings)
  161. }
  162. }
  163. function add(value) {
  164. if (typeof value === 'function') {
  165. addPlugin(value)
  166. } else if (typeof value === 'object') {
  167. if ('length' in value) {
  168. addPlugin.apply(null, value)
  169. } else {
  170. addPreset(value)
  171. }
  172. } else {
  173. throw new Error('Expected usable value, not `' + value + '`')
  174. }
  175. }
  176. function addList(plugins) {
  177. var index = -1
  178. if (plugins === null || plugins === undefined) {
  179. // Empty.
  180. } else if (typeof plugins === 'object' && 'length' in plugins) {
  181. while (++index < plugins.length) {
  182. add(plugins[index])
  183. }
  184. } else {
  185. throw new Error('Expected a list of plugins, not `' + plugins + '`')
  186. }
  187. }
  188. function addPlugin(plugin, value) {
  189. var entry = find(plugin)
  190. if (entry) {
  191. if (plain(entry[1]) && plain(value)) {
  192. value = extend(true, entry[1], value)
  193. }
  194. entry[1] = value
  195. } else {
  196. attachers.push(slice.call(arguments))
  197. }
  198. }
  199. }
  200. function find(plugin) {
  201. var index = -1
  202. while (++index < attachers.length) {
  203. if (attachers[index][0] === plugin) {
  204. return attachers[index]
  205. }
  206. }
  207. }
  208. // Parse a file (in string or vfile representation) into a unist node using
  209. // the `Parser` on the processor.
  210. function parse(doc) {
  211. var file = vfile(doc)
  212. var Parser
  213. freeze()
  214. Parser = processor.Parser
  215. assertParser('parse', Parser)
  216. if (newable(Parser, 'parse')) {
  217. return new Parser(String(file), file).parse()
  218. }
  219. return Parser(String(file), file) // eslint-disable-line new-cap
  220. }
  221. // Run transforms on a unist node representation of a file (in string or
  222. // vfile representation), async.
  223. function run(node, file, cb) {
  224. assertNode(node)
  225. freeze()
  226. if (!cb && typeof file === 'function') {
  227. cb = file
  228. file = null
  229. }
  230. if (!cb) {
  231. return new Promise(executor)
  232. }
  233. executor(null, cb)
  234. function executor(resolve, reject) {
  235. transformers.run(node, vfile(file), done)
  236. function done(error, tree, file) {
  237. tree = tree || node
  238. if (error) {
  239. reject(error)
  240. } else if (resolve) {
  241. resolve(tree)
  242. } else {
  243. cb(null, tree, file)
  244. }
  245. }
  246. }
  247. }
  248. // Run transforms on a unist node representation of a file (in string or
  249. // vfile representation), sync.
  250. function runSync(node, file) {
  251. var result
  252. var complete
  253. run(node, file, done)
  254. assertDone('runSync', 'run', complete)
  255. return result
  256. function done(error, tree) {
  257. complete = true
  258. result = tree
  259. bail(error)
  260. }
  261. }
  262. // Stringify a unist node representation of a file (in string or vfile
  263. // representation) into a string using the `Compiler` on the processor.
  264. function stringify(node, doc) {
  265. var file = vfile(doc)
  266. var Compiler
  267. freeze()
  268. Compiler = processor.Compiler
  269. assertCompiler('stringify', Compiler)
  270. assertNode(node)
  271. if (newable(Compiler, 'compile')) {
  272. return new Compiler(node, file).compile()
  273. }
  274. return Compiler(node, file) // eslint-disable-line new-cap
  275. }
  276. // Parse a file (in string or vfile representation) into a unist node using
  277. // the `Parser` on the processor, then run transforms on that node, and
  278. // compile the resulting node using the `Compiler` on the processor, and
  279. // store that result on the vfile.
  280. function process(doc, cb) {
  281. freeze()
  282. assertParser('process', processor.Parser)
  283. assertCompiler('process', processor.Compiler)
  284. if (!cb) {
  285. return new Promise(executor)
  286. }
  287. executor(null, cb)
  288. function executor(resolve, reject) {
  289. var file = vfile(doc)
  290. pipeline.run(processor, {file: file}, done)
  291. function done(error) {
  292. if (error) {
  293. reject(error)
  294. } else if (resolve) {
  295. resolve(file)
  296. } else {
  297. cb(null, file)
  298. }
  299. }
  300. }
  301. }
  302. // Process the given document (in string or vfile representation), sync.
  303. function processSync(doc) {
  304. var file
  305. var complete
  306. freeze()
  307. assertParser('processSync', processor.Parser)
  308. assertCompiler('processSync', processor.Compiler)
  309. file = vfile(doc)
  310. process(file, done)
  311. assertDone('processSync', 'process', complete)
  312. return file
  313. function done(error) {
  314. complete = true
  315. bail(error)
  316. }
  317. }
  318. }
  319. // Check if `value` is a constructor.
  320. function newable(value, name) {
  321. return (
  322. typeof value === 'function' &&
  323. value.prototype &&
  324. // A function with keys in its prototype is probably a constructor.
  325. // Classes’ prototype methods are not enumerable, so we check if some value
  326. // exists in the prototype.
  327. (keys(value.prototype) || name in value.prototype)
  328. )
  329. }
  330. // Check if `value` is an object with keys.
  331. function keys(value) {
  332. var key
  333. for (key in value) {
  334. return true
  335. }
  336. return false
  337. }
  338. // Assert a parser is available.
  339. function assertParser(name, Parser) {
  340. if (typeof Parser !== 'function') {
  341. throw new Error('Cannot `' + name + '` without `Parser`')
  342. }
  343. }
  344. // Assert a compiler is available.
  345. function assertCompiler(name, Compiler) {
  346. if (typeof Compiler !== 'function') {
  347. throw new Error('Cannot `' + name + '` without `Compiler`')
  348. }
  349. }
  350. // Assert the processor is not frozen.
  351. function assertUnfrozen(name, frozen) {
  352. if (frozen) {
  353. throw new Error(
  354. 'Cannot invoke `' +
  355. name +
  356. '` on a frozen processor.\nCreate a new processor first, by invoking it: use `processor()` instead of `processor`.'
  357. )
  358. }
  359. }
  360. // Assert `node` is a unist node.
  361. function assertNode(node) {
  362. if (!node || typeof node.type !== 'string') {
  363. throw new Error('Expected node, got `' + node + '`')
  364. }
  365. }
  366. // Assert that `complete` is `true`.
  367. function assertDone(name, asyncName, complete) {
  368. if (!complete) {
  369. throw new Error(
  370. '`' + name + '` finished async. Use `' + asyncName + '` instead'
  371. )
  372. }
  373. }