Package Exports
- restream
- restream/build
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (restream) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
restream
Regular expression detection implemented as a Transform
steam; and regex-based buffer replacement stream to replace incoming data on-the-fly.
yarn add -E restream
Table of Contents
API
The package contains the default restream
and Replaceable
functions.
import restream, { Replaceable } from 'restream'
restream(
regex: RegExp,
): Transform
Create a Transform
stream which will buffer incoming data and push regex results when matches can be made, i.e. when regex.exec
returns non-null value. When the g
flag is added to the regex, multiple matches will be detected.
/** yarn example/restream.js */
import restream from 'restream'
import { createReadable, createWritable } from './lib'
(async () => {
try {
const rs = createReadable('test-string-{12345}-{67890}')
const stream = restream(/{(\d+)}/g) // create a transform stream
rs.pipe(stream)
const { data, ws } = createWritable()
stream.pipe(ws)
ws.once('finish', () => {
console.log(data)
})
} catch (err) {
console.error(err)
}
})()
[ [ '{12345}',
'12345',
index: 12,
input: 'test-string-{12345}-{67890}' ],
[ '{67890}',
'67890',
index: 20,
input: 'test-string-{12345}-{67890}' ] ]
Replaceable
Class
A Replaceable transform stream can be used to transform data according to a single or multiple rules.
Rule
Type
Replaceable uses rules to determine how to transform data. Below is the description of the Rule
type.
Property | Type | Description | Example |
---|---|---|---|
re* |
RegExp | A regular expression. | Detect inline code blocks in markdown: /`(.+?)`/ . |
replacement* |
string | function | async function | A replacer either as a string, function, or async function. It will be passed to the string.replace(re, replacement) native JavaScript method. |
As a string: INLINE_CODE . |
String
Replacement
Replacement as a string. Given a simple string, it will replace a match detected by the rule's regular expression, without consideration for the capturing groups.
Function
Replacer
Replacement as a function. See MDN for more documentation on how the replacer function should be implemented.
The example below allows to replace strings like %NPM: documentary%
and %NPM: @rqt/aqt%
into a markdown badge (used in documentary
).
const syncRule = {
re: /^%NPM: ((?:[@\w\d-_]+\/)?[\w\d-_]+)%$/gm,
replacement(match, name) {
const n = encodeURIComponent(name)
const svg = `https://badge.fury.io/js/${n}.svg`
const link = `https://npmjs.org/package/${name}`
return `[](${link})`
},
} |
Async Function
Replacer
An asynchronous function to get replacements. The stream won't push any data until the replacer's promise is resolved. Due to implementation details, the regex will have to be run against incoming chunks twice, therefore it might be not ideal for heavy-load applications with many matches.
This example will replace strings like %FORK-js: example example/Replaceable.js%
into the output of a forked JavaScript program (used in documentary
).
import { fork } from 'spawncommand'
const codeSurround = (m, lang = '') =>
`\`\`\`${lang}\n${m.trim()}\n\`\`\``
const forkRule = {
re: /%FORK(?:-(\w+))? (.+)%/mg,
async replacement(match, lang, m) {
const [mod, ...args] = m.split(' ')
const { promise } = fork(mod, args, {
execArgv: [],
stdio: 'pipe',
})
const { stdout } = await promise
return codeSurround(stdout, lang)
},
} |
constructor(
rule: Rule|Rules[],
): Replaceable
Create a Transform
stream which will make data available when an incoming chunk has been updated according to the specified rule or rules.
Matches can be replaced using a string, function or async function. When multiple rules are passed as an array, the string will be replaced multiple times if the latter rules also modify the data.
/** yarn example/Replaceable.js */
import Catchment from 'catchment'
import { Replaceable } from 'restream'
import { createReadable } from './lib'
const dateRule = {
re: /%DATE%/g,
replacement: new Date().toLocaleString(),
}
const emRule = {
re: /__(.+?)__/g,
replacement(match, p1) {
return `<em>${p1}</em>`
},
}
const authorRule = {
re: /^%AUTHOR_ID: (.+?)%$/mg,
async replacement(match, id) {
const name = await new Promise(resolve => {
// pretend to lookup author name from the database
const authors = { 5: 'John' }
resolve(authors[id])
})
return `Author: <strong>${name}</strong>`
},
}
const STRING = `
Hello __Fred__, your username is __fred__.
You have __5__ stars.
%AUTHOR_ID: 5%
on __%DATE%__
`
;(async () => {
try {
const replaceable = new Replaceable([
dateRule,
emRule,
authorRule,
])
const rs = createReadable(STRING)
rs.pipe(replaceable)
const { promise } = new Catchment({
rs: replaceable,
})
const res = await promise
console.log(res)
} catch (err) {
console.error(err)
}
})()
Output:
Hello <em>Fred</em>, your username is <em>fred</em>.
You have <em>5</em> stars.
Author: <strong>John</strong>
on <em>2018-7-21 02:09:39</em>
Replacer
Context
Replacer functions will be executed with their context set to a Replaceable instance to which they belong. Both sync
and async
replacers can use the this
keyword to access their Replaceable instance and modify its properties and/or emit events. This is done so that there's a mechanism by which replacers can share data between themselves.
For example, we might want to read and parse an external file first, but remember its data for use in following replacers.
Given an external file example/types.json
:
{
"TypeA": "A new type with certain properties.",
"TypeB": "A type to represent the state of the world."
}
Replaceable can read it in the first typesRule
rule, and reference its data in the second paramRule
rule:
/** yarn example/context.js */
import Catchment from 'catchment'
import { createReadStream } from 'fs'
import { Replaceable } from 'restream'
import { createReadable } from './lib'
const typesRule = {
re: /^%types: (.+?)%$/mg,
async replacement(match, location) {
const rs = createReadStream(location)
const { promise } = new Catchment({ rs })
const d = await promise
const j = JSON.parse(d)
this.types = j // remember types for access in following rules
return match
},
}
const paramRule = {
re: /^ \* @typedef {(.+?)} (.+)(?: .*)?/mg,
replacement(match, type, typeName) {
const description = this.types[typeName]
if (!description) return match
return ` * @typedef {${type}} ${typeName} ${description}`
},
}
const STRING = `
%types: example/types.json%
/**
* @typedef {Object} TypeA
*/
`
;(async () => {
try {
const replaceable = new Replaceable([
typesRule,
paramRule,
])
const rs = createReadable(STRING)
rs.pipe(replaceable)
const { promise } = new Catchment({
rs: replaceable,
})
const res = await promise
console.log(res)
} catch (err) {
console.error(err)
}
})()
%types: example/types.json%
/**
* @typedef {Object} TypeA A new type with certain properties.
*/
As can be seen above, the description of the type was automatically updated based on the data read from the file.
Replacer
Errors
If an error happens in a sync
or async
replacer function, the Replaceable
will emit it and close.
/** yarn example/errors.js */
import { Replaceable } from 'restream'
import { createReadable } from './lib'
const STRING = 'test string'
;(async () => {
try {
const rs = createReadable(STRING)
const replaceable = new Replaceable([
{
re: /.*/,
replacement() {
throw new Error('An error occurred during a replacement.')
},
},
])
await new Promise((resolve, reject) => {
replaceable
.on('close', resolve)
.on('error', reject)
rs.pipe(replaceable)
})
} catch ({ message }) {
console.log(message)
}
})()
An error occurred during a replacement.
(c) Art Deco 2018