142 lines
3.5 KiB
JavaScript
142 lines
3.5 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var escapeRegExp = require('escape-string-regexp');
|
||
|
var objectAssign = require('object-assign');
|
||
|
var Transform = require('readable-stream/transform');
|
||
|
|
||
|
module.exports = function ReplaceStream(search, replace, options) {
|
||
|
var tail = '';
|
||
|
var totalMatches = 0;
|
||
|
var isRegex = search instanceof RegExp;
|
||
|
|
||
|
options = objectAssign({
|
||
|
limit: Infinity,
|
||
|
encoding: 'utf8',
|
||
|
maxMatchLen: 100
|
||
|
}, options);
|
||
|
|
||
|
var replaceFn = replace;
|
||
|
|
||
|
replaceFn = createReplaceFn(replace, isRegex);
|
||
|
|
||
|
var match;
|
||
|
if (isRegex) {
|
||
|
match = matchFromRegex(search, options)
|
||
|
} else {
|
||
|
match = matchFromString(search, options);
|
||
|
options.maxMatchLen = search.length;
|
||
|
}
|
||
|
|
||
|
function transform(buf, enc, cb) {
|
||
|
var matches;
|
||
|
var lastPos = 0;
|
||
|
var runningMatch = '';
|
||
|
var matchCount = 0;
|
||
|
var rewritten = '';
|
||
|
var haystack = tail + buf.toString(options.encoding);
|
||
|
tail = '';
|
||
|
|
||
|
while (totalMatches < options.limit &&
|
||
|
(matches = match.exec(haystack)) !== null) {
|
||
|
|
||
|
matchCount++;
|
||
|
var before = haystack.slice(lastPos, matches.index);
|
||
|
var regexMatch = matches;
|
||
|
lastPos = matches.index + regexMatch[0].length;
|
||
|
|
||
|
if (lastPos > haystack.length && regexMatch[0].length < options.maxMatchLen) {
|
||
|
tail = regexMatch[0]
|
||
|
} else {
|
||
|
var dataToAppend = getDataToAppend(before,regexMatch);
|
||
|
rewritten += dataToAppend;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (tail.length < 1)
|
||
|
tail = haystack.slice(lastPos).length > options.maxMatchLen ? haystack.slice(lastPos).slice(0 - options.maxMatchLen) : haystack.slice(lastPos)
|
||
|
|
||
|
var dataToQueue = getDataToQueue(matchCount,haystack,rewritten,lastPos);
|
||
|
cb(null, dataToQueue);
|
||
|
}
|
||
|
|
||
|
function getDataToAppend(before, match) {
|
||
|
var dataToAppend = before;
|
||
|
|
||
|
totalMatches++;
|
||
|
|
||
|
dataToAppend += isRegex ? replaceFn.apply(this, match.concat([match.index, match.input])) : replaceFn(match[0]);
|
||
|
|
||
|
return dataToAppend;
|
||
|
}
|
||
|
|
||
|
function getDataToQueue(matchCount, haystack, rewritten, lastPos) {
|
||
|
if (matchCount > 0) {
|
||
|
if (haystack.length > tail.length) {
|
||
|
return rewritten + haystack.slice(lastPos, haystack.length - tail.length);
|
||
|
}
|
||
|
|
||
|
return rewritten;
|
||
|
}
|
||
|
|
||
|
return haystack.slice(0, haystack.length - tail.length);
|
||
|
}
|
||
|
|
||
|
function flush(cb) {
|
||
|
if (tail) {
|
||
|
this.push(tail);
|
||
|
}
|
||
|
cb();
|
||
|
}
|
||
|
|
||
|
return new Transform({
|
||
|
transform: transform,
|
||
|
flush: flush
|
||
|
});
|
||
|
};
|
||
|
|
||
|
function createReplaceFn(replace, isRegEx) {
|
||
|
var regexReplaceFunction = function () {
|
||
|
var newReplace = replace;
|
||
|
// ability to us $1 with captures
|
||
|
// Start at 1 and end at length - 2 to avoid the match parameter and offset
|
||
|
// And string parameters
|
||
|
var paramLength = arguments.length - 2;
|
||
|
for (var i = 1; i < paramLength; i++) {
|
||
|
newReplace = newReplace.replace(new RegExp('\\$' + i, 'g'), arguments[i] || '')
|
||
|
}
|
||
|
return newReplace;
|
||
|
};
|
||
|
|
||
|
if (isRegEx && !(replace instanceof Function)) {
|
||
|
return regexReplaceFunction;
|
||
|
}
|
||
|
|
||
|
if (!(replace instanceof Function)) {
|
||
|
return function stringReplaceFunction() {
|
||
|
return replace;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return replace;
|
||
|
}
|
||
|
|
||
|
function matchFromRegex(regex, options) {
|
||
|
if (options.regExpOptions) {
|
||
|
regex = new RegExp(regex.source, options.regExpOptions)
|
||
|
}
|
||
|
|
||
|
// If there is no global flag then there can only be one match
|
||
|
if (!regex.global) {
|
||
|
options.limit = 1;
|
||
|
}
|
||
|
return regex;
|
||
|
}
|
||
|
|
||
|
function matchFromString(s, options) {
|
||
|
if (options.regExpOptions) {
|
||
|
return new RegExp(escapeRegExp(s), options.regExpOptions);
|
||
|
}
|
||
|
|
||
|
return new RegExp(escapeRegExp(s), options.ignoreCase === false ? 'gm' : 'gmi');
|
||
|
}
|