JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3105
  • Score
    100M100P100Q117261F
  • License MIT

Regular Expression Detection & Replacement streams

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

npm version

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 `[![npm version](${svg})](${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