UNPKG

3.32 kBJavaScriptView Raw
1const { Transform } = require('stream');
2const { checkRule } = require('./lib');
3let cleanStack = require('@artdeco/clean-stack'); if (cleanStack && cleanStack.__esModule) cleanStack = cleanStack.default;
4
5/**
6 * @typedef {import('.').Rule} Rule
7 */
8
9 class Replaceable extends Transform {
10 /**
11 * Replaceable class that extends Transform and pushes data when it's done replacing each incoming chunk. If the replacement is passed as a function, it will work in the same way as the replacer for `string.replace` method (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), taking the `match` as the first argument, and matched `p1`, `p2`, _etc_ parameters as following arguments. The replacer can also be an async function.
12 * @constructor
13 * @param {Rule|Rule[]} rules A single replacement rule, or multiple rules.
14 * @example
15 *
16 * // markdown __ to html emphasise implementation
17 * const stream = replaceStream({
18 * re: /__(\S+)__/g,
19 * replacement(match, p1) {
20 * return `<em>${p1}</em>`
21 * },
22 * })
23 */
24 constructor(rules) {
25 super()
26 const re = Array.isArray(rules) ? rules : [rules]
27 const fr = re.filter(checkRule)
28 this.rules = fr
29 }
30
31 /**
32 * Stop executing further after the current rule.
33 */
34 brake() {
35 this._broke = true
36 }
37
38 async reduce(chunk) {
39 /** @type {string} */
40 const s = await this.rules.reduce(async (acc, { re, replacement }) => {
41 /** @type {string} */
42 let string = await acc
43 if (this._broke) return string
44
45 if (typeof replacement == 'string') {
46 string = string.replace(re, replacement)
47 } else {
48 /** @type {function} */
49 const R = replacement.bind(this)
50 const promises = []
51 let commonError
52 const t = string.replace(re, (match, ...args) => {
53 commonError = new Error()
54 try {
55 if (this._broke) return match
56 const p = R(match, ...args)
57 if (p instanceof Promise) {
58 promises.push(p)
59 }
60 return p
61 } catch (e) { // hide stack for sync stack traces
62 hideStack(commonError, e)
63 }
64 })
65 if (promises.length) {
66 try { // hide stack only for when throw happens before awaits
67 const data = await Promise.all(promises)
68 string = string.replace(re, () => data.shift())
69 } catch (e) {
70 hideStack(commonError, e)
71 }
72 } else {
73 string = t
74 }
75 }
76 return string
77 }, `${chunk}`)
78
79 return s
80 }
81 async _transform(chunk, _, next) {
82 try {
83 const s = await this.reduce(chunk)
84 this.push(s)
85 next()
86 } catch (e) {
87 const s = cleanStack(e.stack)
88 e.stack = s
89 next(e)
90 }
91 }
92}
93
94const hideStack = (commonError, thrownError) => {
95 if (!(thrownError instanceof Error)) throw thrownError
96 const [, , commonLine] = commonError.stack.split('\n', 3)
97 const i = thrownError.stack.indexOf(commonLine)
98 if (i == -1) throw thrownError
99 const stack = thrownError.stack.substr(0, i - 1)
100 const li = stack.lastIndexOf('\n')
101 thrownError.stack = stack.substr(0, li)
102 throw thrownError
103}
104
105module.exports = Replaceable
106//# sourceMappingURL=Replaceable.js.map
\No newline at end of file