Comparing version 2.0.3 to 2.1.0
@@ -8,2 +8,3 @@ "use strict"; | ||
exports.replaceStream = replaceStream; | ||
exports.Replaceable = void 0; | ||
@@ -47,3 +48,3 @@ var _stream = require("stream"); | ||
* Create a replacement stream, which will push data when it's done replacing each incoming chunk. The return is a transform stream which writes strings. 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. | ||
* @param {RegexObject|RegexObject[]} rules A single replacement rule, or multiple rules. | ||
* @param {Rule|Rule[]} rules A single replacement rule, or multiple rules. | ||
* @example | ||
@@ -62,26 +63,75 @@ * | ||
function replaceStream(rules) { | ||
const re = Array.isArray(rules) ? rules : [rules]; | ||
const fr = re.filter(_lib.checkRegexObject); | ||
const ts = new _stream.Transform({ | ||
transform(chunk, _, next) { | ||
let string = `${chunk}`; | ||
fr.forEach(({ | ||
re, | ||
replacement | ||
}) => { | ||
const ts = new Replaceable(rules); | ||
return ts; | ||
} | ||
class Replaceable extends _stream.Transform { | ||
/** | ||
* 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. | ||
* @constructor | ||
* @param {Rule|Rule[]} rules A single replacement rule, or multiple rules. | ||
* @example | ||
* | ||
* // markdown __ to html emphasise implementation | ||
* const stream = replaceStream({ | ||
* re: /__(\S+)__/g, | ||
* replacement(match, p1) { | ||
* return `<em>${p1}</em>` | ||
* }, | ||
* }) | ||
*/ | ||
constructor(rules) { | ||
super(); | ||
const re = Array.isArray(rules) ? rules : [rules]; | ||
const fr = re.filter(_lib.checkRule); | ||
this.rules = fr; | ||
} | ||
async _transform(chunk, _, next) { | ||
const s = await this.rules.reduce(async (acc, { | ||
re, | ||
replacement | ||
}) => { | ||
/** @type {string} */ | ||
let string = await acc; | ||
if (typeof replacement == 'string') { | ||
string = string.replace(re, replacement); | ||
}); | ||
ts.push(string); | ||
next(); | ||
} | ||
} else { | ||
const promises = []; | ||
const t = string.replace(re, (match, ...args) => { | ||
const p = replacement(match, ...args); | ||
}); | ||
return ts; | ||
if (p instanceof Promise) { | ||
promises.push(p); | ||
} | ||
return p; | ||
}); | ||
if (promises.length) { | ||
const data = await Promise.all(promises); | ||
string = string.replace(re, () => data.shift()); | ||
} else { | ||
string = t; | ||
} | ||
} | ||
return string; | ||
}, `${chunk}`); | ||
this.push(s); | ||
next(); | ||
} | ||
} | ||
/** | ||
* @typedef {(substring: string, ...args: any[]) => string} Replacer | ||
* @typedef {(match: string, ...params: string[]) => string} Replacer | ||
* @typedef {(match: string, ...params: string[]) => Promise.<string>} AsyncReplacer | ||
* | ||
* @typedef {Object} RegexObject | ||
* @typedef {Object} Rule | ||
* @property {RegExp} re Regular expression to match against | ||
* @property {string|Replacer} replacement A replacement string, or a replacement string function. | ||
*/ | ||
* @property {string|Replacer|AsyncReplacer} replacement A replacement string, or a replacement string function. | ||
*/ | ||
exports.Replaceable = Replaceable; |
@@ -0,1 +1,7 @@ | ||
## 20 June 2018 | ||
### 2.1.0 | ||
- [feature] `async` replacer, `Replaceable` class to replace `replaceStream`. | ||
## 15 June 2018 | ||
@@ -2,0 +8,0 @@ |
{ | ||
"name": "restream", | ||
"version": "2.0.3", | ||
"version": "2.1.0", | ||
"description": "Regular Expression Detection & Replacement streams", | ||
@@ -13,5 +13,6 @@ "main": "build", | ||
"e": "node examples/run", | ||
"doc": "doc -t README-source.md -r -o README.md", | ||
"examples/replace-function.js": "yarn e examples/replace-function", | ||
"doc": "doc README-source.md -o README.md", | ||
"examples/replacer.js": "yarn e examples/replacer", | ||
"examples/replace-stream.js": "yarn e examples/replace-stream", | ||
"examples/Replaceable.js": "yarn e examples/Replaceable", | ||
"examples/restream.js": "yarn e examples/restream" | ||
@@ -51,3 +52,3 @@ }, | ||
"catchment": "2.0.1", | ||
"documentary": "1.0.1", | ||
"documentary": "1.3.1", | ||
"snapshot-context": "2.0.1", | ||
@@ -54,0 +55,0 @@ "spawncommand": "2.0.1", |
# restream | ||
[![npm version](https://badge.fury.io/js/restream.svg)](https://badge.fury.io/js/restream) | ||
%NPM: restream% | ||
Regular expression detection implemented as a `Transform` steam; and regex-based buffer replacement stream to replace incoming data on-the-fly. | ||
``` | ||
@@ -9,8 +11,3 @@ yarn add -E restream | ||
Regular expression detection implemented as a transform Steam and regex-based buffer replacement stream to replace streamed data on-the-fly. | ||
```js | ||
import restream, { replaceStream } from 'restream' | ||
``` | ||
## Table of Contents | ||
@@ -20,9 +17,19 @@ | ||
## `restream(regex: RegExp) => Transform` | ||
## API | ||
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. You will probably want to | ||
set the `g` flag on regexes most of the time. | ||
The package contains the default `restream` and `Replaceable` functions. | ||
```js | ||
import restream, { Replaceable } from 'restream' | ||
``` | ||
```### restream => Transform | ||
[ | ||
["regex", "RegExp"] | ||
] | ||
``` | ||
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. | ||
```js | ||
/** yarn examples/restream.js **/ | ||
@@ -62,14 +69,34 @@ import restream from 'restream' | ||
## `replaceStream({re:RegExp, replacement:string }[]) => Transform` | ||
### `Replaceable` | ||
Creates a `Transform` stream which will make data available | ||
when an incoming chunk has been updated according to the `regex` input, which can | ||
be either a single regex object (`{ re: /regex/, replacement: 'hello-world' }`), | ||
or an array of such objects. A `replacement` can be either a string, or a function, | ||
which will be called by `str.replace` | ||
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. | ||
```table | ||
[ | ||
["Property", "Type", "Description"], | ||
["re", "`RegExp`", "A regular expression."], | ||
["replacement (1)", "`string`", "Replacement as a string. Here and below, it will be passed to the `string.replace(re, replacement)` native JavaScript method."], | ||
["replacement (2)", "`function`", "Replacement as a sync function. See [MDN][2] for more documentation on how the replacer function should be implemented."], | ||
["replacement (3)", "`async function`", "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."] | ||
] | ||
``` | ||
```#### constructor => Replaceable | ||
[ | ||
["rule", "Rule|Rules[]"] | ||
] | ||
``` | ||
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. | ||
```js | ||
/** yarn examples/replace-stream.js */ | ||
/** yarn examples/Replaceable.js */ | ||
import Catchment from 'catchment' | ||
import { replaceStream } from 'restream' | ||
import { Replaceable } from 'restream' | ||
import { createReadable } from './lib' | ||
@@ -79,19 +106,36 @@ | ||
try { | ||
const stream = replaceStream([{ | ||
re: /{{ user }}/, | ||
replacement: 'fred', | ||
}, { | ||
re: /{{ name }}/g, | ||
replacement: 'Fred', | ||
}, { | ||
re: /{{ stars }}/, | ||
replacement: '5', | ||
}]) | ||
const dateRule = { | ||
re: /%DATE%/g, | ||
replacement: new Date().toLocaleString(), | ||
} | ||
const emRule = { | ||
re: /__(.+?)__/g, | ||
replacement(match, p1) { | ||
return `<em>${p1}</em>` | ||
}, | ||
} | ||
const authorRule = { | ||
re: /^%AUTHOR: (.+?)%$/mg, | ||
async replacement(match, p1) { | ||
await new Promise(r => setTimeout(r,100)) | ||
return `Author: <strong>${p1}</strong>` | ||
}, | ||
} | ||
const replaceable = new Replaceable([ | ||
dateRule, | ||
emRule, | ||
authorRule, | ||
]) | ||
const rs = createReadable(` | ||
Hello __Fred__, your username is __fred__ and you have __5__ stars. | ||
%AUTHOR: John% | ||
on __%DATE%__ | ||
`) | ||
rs.pipe(replaceable) | ||
const rs = createReadable('Hello {{ name }}, your username is {{ user }} and you have {{ stars }} stars') | ||
rs.pipe(stream) | ||
const { promise } = new Catchment({ | ||
rs: stream, | ||
rs: replaceable, | ||
}) | ||
const res = await promise | ||
console.log(res) | ||
@@ -107,13 +151,19 @@ } catch (err) { | ||
```fs | ||
Hello Fred, your username is fred and you have 5 stars | ||
Hello <em>Fred</em>, your username is <em>fred</em> and you have <em>5</em> stars. | ||
Author: <strong>John</strong> | ||
on <em>2018-6-20 19:59:18</em> | ||
``` | ||
## `replaceStream({re:RegExp, replacement: function(match, ...params) }[]) => Transform` | ||
```#### DEPRECATED_replaceStream => Transform | ||
[ | ||
["rule", "Rule|Rule[]"] | ||
] | ||
``` | ||
You can replace matches using a function. See [MDN][2] for more documentation. | ||
Used to create a `Replaceable` stream. Deprecated in favour of the class constructor (see above). | ||
```js | ||
/** yarn examples/replace-function.js */ | ||
import { replaceStream } from '../src' | ||
/** yarn examples/replace-stream.js */ | ||
import Catchment from 'catchment' | ||
import { replaceStream } from 'restream' | ||
import { createReadable } from './lib' | ||
@@ -124,10 +174,14 @@ | ||
const stream = replaceStream([{ | ||
re: /__(\S+)__/g, | ||
replacement(match, p1) { | ||
return `<em>${p1}</em>` | ||
}, | ||
re: /{{ user }}/, | ||
replacement: 'fred', | ||
}, { | ||
re: /{{ name }}/g, | ||
replacement: 'Fred', | ||
}, { | ||
re: /{{ stars }}/, | ||
replacement: '5', | ||
}]) | ||
const rs = createReadable('Hello __Fred__, your username is __fred__ and you have __5__ stars.') | ||
const rs = createReadable('Hello {{ name }}, your username is {{ user }} and you have {{ stars }} stars') | ||
rs.pipe(stream) | ||
const { promise } = new Catchment({ | ||
@@ -137,3 +191,2 @@ rs: stream, | ||
const res = await promise | ||
console.log(res) | ||
@@ -149,4 +202,10 @@ } catch (err) { | ||
```fs | ||
Hello <em>Fred</em>, your username is <em>fred</em> and you have <em>5</em> stars | ||
Hello Fred, your username is fred and you have 5 stars | ||
``` | ||
<!-- , which can | ||
be either a single regex object (`{ re: /regex/, replacement: 'hello-world' }`), | ||
or an array of such objects. A `replacement` can be either a string, or a function, | ||
which will be called by `str.replace` --> | ||
--- | ||
@@ -153,0 +212,0 @@ |
136
README.md
# restream | ||
[![npm version](https://badge.fury.io/js/restream.svg)](https://badge.fury.io/js/restream) | ||
[![npm version](https://badge.fury.io/js/restream.svg)](https://npmjs.org/package/restream) | ||
Regular expression detection implemented as a `Transform` steam; and regex-based buffer replacement stream to replace incoming data on-the-fly. | ||
``` | ||
@@ -9,22 +11,26 @@ yarn add -E restream | ||
Regular expression detection implemented as a transform Steam and regex-based buffer replacement stream to replace streamed data on-the-fly. | ||
```js | ||
import restream, { replaceStream } from 'restream' | ||
``` | ||
## Table of Contents | ||
- [Table of Contents](#table-of-contents) | ||
- [`restream(regex: RegExp) => Transform`](#restreamregex-regexp--transform) | ||
- [`replaceStream({re:RegExp, replacement:string }[]) => Transform`](#replacestreamreregexp-replacementstring---transform) | ||
- [`replaceStream({re:RegExp, replacement: function(match, ...params) }[]) => Transform`](#replacestreamreregexp-replacement-functionmatch-params---transform) | ||
- [API](#api) | ||
* [`restream(regex: RegExp): Transform`](#restreamregex-regexp-transform) | ||
* [`Replaceable`](#replaceable) | ||
* [`Rule` Type](#rule-type) | ||
* [`constructor(rule: Rule|Rules[]): Replaceable`](#constructorrule-rulerules-replaceable) | ||
* [`DEPRECATED_replaceStream(rule: Rule|Rule[]): Transform`](#deprecated_replacestreamrule-rulerule-transform) | ||
## `restream(regex: RegExp) => Transform` | ||
## API | ||
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. You will probably want to | ||
set the `g` flag on regexes most of the time. | ||
The package contains the default `restream` and `Replaceable` functions. | ||
```js | ||
import restream, { Replaceable } from 'restream' | ||
``` | ||
### `restream(`<br/> `regex: RegExp,`<br/>`): 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. | ||
```js | ||
/** yarn examples/restream.js **/ | ||
@@ -64,14 +70,27 @@ import restream from 'restream' | ||
## `replaceStream({re:RegExp, replacement:string }[]) => Transform` | ||
### `Replaceable` | ||
Creates a `Transform` stream which will make data available | ||
when an incoming chunk has been updated according to the `regex` input, which can | ||
be either a single regex object (`{ re: /regex/, replacement: 'hello-world' }`), | ||
or an array of such objects. A `replacement` can be either a string, or a function, | ||
which will be called by `str.replace` | ||
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 | | ||
| -------- | ---- | ----------- | | ||
| re | `RegExp` | A regular expression. | | ||
| replacement (1) | `string` | Replacement as a string. Here and below, it will be passed to the `string.replace(re, replacement)` native JavaScript method. | | ||
| replacement (2) | `function` | Replacement as a sync function. See [MDN][2] for more documentation on how the replacer function should be implemented. | | ||
| replacement (3) | `async function` | 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. | | ||
#### `constructor(`<br/> `rule: Rule|Rules[],`<br/>`): 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. | ||
```js | ||
/** yarn examples/replace-stream.js */ | ||
/** yarn examples/Replaceable.js */ | ||
import Catchment from 'catchment' | ||
import { replaceStream } from 'restream' | ||
import { Replaceable } from 'restream' | ||
import { createReadable } from './lib' | ||
@@ -81,19 +100,36 @@ | ||
try { | ||
const stream = replaceStream([{ | ||
re: /{{ user }}/, | ||
replacement: 'fred', | ||
}, { | ||
re: /{{ name }}/g, | ||
replacement: 'Fred', | ||
}, { | ||
re: /{{ stars }}/, | ||
replacement: '5', | ||
}]) | ||
const dateRule = { | ||
re: /%DATE%/g, | ||
replacement: new Date().toLocaleString(), | ||
} | ||
const emRule = { | ||
re: /__(.+?)__/g, | ||
replacement(match, p1) { | ||
return `<em>${p1}</em>` | ||
}, | ||
} | ||
const authorRule = { | ||
re: /^%AUTHOR: (.+?)%$/mg, | ||
async replacement(match, p1) { | ||
await new Promise(r => setTimeout(r,100)) | ||
return `Author: <strong>${p1}</strong>` | ||
}, | ||
} | ||
const replaceable = new Replaceable([ | ||
dateRule, | ||
emRule, | ||
authorRule, | ||
]) | ||
const rs = createReadable(` | ||
Hello __Fred__, your username is __fred__ and you have __5__ stars. | ||
%AUTHOR: John% | ||
on __%DATE%__ | ||
`) | ||
rs.pipe(replaceable) | ||
const rs = createReadable('Hello {{ name }}, your username is {{ user }} and you have {{ stars }} stars') | ||
rs.pipe(stream) | ||
const { promise } = new Catchment({ | ||
rs: stream, | ||
rs: replaceable, | ||
}) | ||
const res = await promise | ||
console.log(res) | ||
@@ -109,13 +145,15 @@ } catch (err) { | ||
```fs | ||
Hello Fred, your username is fred and you have 5 stars | ||
Hello <em>Fred</em>, your username is <em>fred</em> and you have <em>5</em> stars. | ||
Author: <strong>John</strong> | ||
on <em>2018-6-20 19:59:18</em> | ||
``` | ||
## `replaceStream({re:RegExp, replacement: function(match, ...params) }[]) => Transform` | ||
#### `DEPRECATED_replaceStream(`<br/> `rule: Rule|Rule[],`<br/>`): Transform` | ||
You can replace matches using a function. See [MDN][2] for more documentation. | ||
Used to create a `Replaceable` stream. Deprecated in favour of the class constructor (see above). | ||
```js | ||
/** yarn examples/replace-function.js */ | ||
import { replaceStream } from '../src' | ||
/** yarn examples/replace-stream.js */ | ||
import Catchment from 'catchment' | ||
import { replaceStream } from 'restream' | ||
import { createReadable } from './lib' | ||
@@ -126,10 +164,14 @@ | ||
const stream = replaceStream([{ | ||
re: /__(\S+)__/g, | ||
replacement(match, p1) { | ||
return `<em>${p1}</em>` | ||
}, | ||
re: /{{ user }}/, | ||
replacement: 'fred', | ||
}, { | ||
re: /{{ name }}/g, | ||
replacement: 'Fred', | ||
}, { | ||
re: /{{ stars }}/, | ||
replacement: '5', | ||
}]) | ||
const rs = createReadable('Hello __Fred__, your username is __fred__ and you have __5__ stars.') | ||
const rs = createReadable('Hello {{ name }}, your username is {{ user }} and you have {{ stars }} stars') | ||
rs.pipe(stream) | ||
const { promise } = new Catchment({ | ||
@@ -139,3 +181,2 @@ rs: stream, | ||
const res = await promise | ||
console.log(res) | ||
@@ -151,4 +192,5 @@ } catch (err) { | ||
```fs | ||
Hello <em>Fred</em>, your username is <em>fred</em> and you have <em>5</em> stars | ||
Hello Fred, your username is fred and you have 5 stars | ||
``` | ||
--- | ||
@@ -155,0 +197,0 @@ |
19561
131
195