website hosted
This commit is contained in:
238
node_modules/stream-replace-string/index.js
generated
vendored
Normal file
238
node_modules/stream-replace-string/index.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
import { Transform, Readable } from 'stream'
|
||||
import { StringDecoder } from 'string_decoder'
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} searchStr
|
||||
* @param {string} replaceWith
|
||||
* @param {number} limit
|
||||
*/
|
||||
const replace = (searchStr, replaceWith, options = {}) => {
|
||||
// Defaulting
|
||||
if (!Object.prototype.hasOwnProperty.call(options, 'limit')) {
|
||||
options.limit = Infinity
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(options, 'bufferReplaceStream')) {
|
||||
options.bufferReplaceStream = true
|
||||
}
|
||||
|
||||
// Type checking
|
||||
if (typeof searchStr !== 'string') {
|
||||
throw new TypeError('searchStr must be a string.')
|
||||
}
|
||||
if (!(
|
||||
typeof replaceWith === 'string' ||
|
||||
replaceWith instanceof Promise ||
|
||||
typeof replaceWith === 'function' ||
|
||||
replaceWith instanceof Readable
|
||||
)) {
|
||||
throw new TypeError('replaceWith must be either a string, a promise resolving a string, a function returning string, a function returning a promise resolving a string, or a readable stream.')
|
||||
}
|
||||
if (typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object.')
|
||||
}
|
||||
if (!(
|
||||
(Number.isInteger(options.limit) && options.limit > 0) ||
|
||||
(options.limit === Infinity)
|
||||
)) {
|
||||
throw new TypeError('options.limit must be a positive integer or infinity.')
|
||||
}
|
||||
if (typeof options.bufferReplaceStream !== 'boolean') {
|
||||
throw new TypeError('options.bufferReplaceStream must be a boolean.')
|
||||
}
|
||||
const limit = options.limit
|
||||
|
||||
// This stuff is for if replaceWith is a readable stream
|
||||
let replaceWithBuffer = ''
|
||||
let replaceWithNewChunk
|
||||
let isDecodingReplaceWithStream = false
|
||||
const startDecodingReplaceWithStream = () => {
|
||||
isDecodingReplaceWithStream = true
|
||||
const stringDecoder = new StringDecoder('utf-8')
|
||||
const dataHandler = data => {
|
||||
replaceWithNewChunk = stringDecoder.write(data)
|
||||
if (options.bufferReplaceStream) {
|
||||
replaceWithBuffer += replaceWithNewChunk
|
||||
}
|
||||
}
|
||||
|
||||
const endHandler = () => {
|
||||
replaceWithNewChunk = stringDecoder.end()
|
||||
if (options.bufferReplaceStream) {
|
||||
replaceWithBuffer += replaceWithNewChunk
|
||||
}
|
||||
}
|
||||
|
||||
replaceWith
|
||||
.on('data', dataHandler)
|
||||
.on('end', endHandler)
|
||||
}
|
||||
if (replaceWith instanceof Readable) {
|
||||
if (options.bufferReplaceStream) {
|
||||
startDecodingReplaceWithStream()
|
||||
} else {
|
||||
replaceWith.pause()
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new string decoder to turn buffers into strings
|
||||
const stringDecoder = new StringDecoder('utf-8')
|
||||
|
||||
// Whether the limit has been reached
|
||||
let doneReplacing = false
|
||||
// Then number of matches
|
||||
let matches = 0
|
||||
|
||||
// The in progress matches waiting for next chunk to be continued
|
||||
/**
|
||||
* An array of numbers, each number is an index which marks the start of the string `unsureBuffer`
|
||||
*/
|
||||
let partialMatchesFromPrevChunk = []
|
||||
|
||||
// The string data that we aren't yet sure it's part of the search string or not
|
||||
// We have to hold on to this until we are sure.
|
||||
let unsureBuffer = ''
|
||||
|
||||
// Get the replace string
|
||||
let replaceStr = typeof replaceWith === 'string' ? replaceWith : undefined
|
||||
const pushReplaceStr = async () => {
|
||||
switch (typeof replaceWith) {
|
||||
case 'string': {
|
||||
transform.push(replaceStr)
|
||||
break
|
||||
}
|
||||
case 'function': {
|
||||
const returnedStr = await replaceWith(matches)
|
||||
if (typeof returnedStr !== 'string') {
|
||||
throw new TypeError('Replace function did not return a string or a promise resolving a string.')
|
||||
}
|
||||
transform.push(returnedStr)
|
||||
break
|
||||
}
|
||||
case 'object': {
|
||||
if (replaceWith instanceof Promise) {
|
||||
replaceStr = await replaceWith
|
||||
if (typeof replaceStr !== 'string') {
|
||||
throw new TypeError('Replace promise did not resolve to a string.')
|
||||
}
|
||||
transform.push(replaceStr)
|
||||
} else if (replaceWith instanceof Readable) {
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!isDecodingReplaceWithStream) {
|
||||
startDecodingReplaceWithStream()
|
||||
}
|
||||
// Push the buffer so far
|
||||
transform.push(replaceWithBuffer)
|
||||
if (!replaceWith.readableEnded) {
|
||||
replaceWith
|
||||
.on('data', () => {
|
||||
transform.push(replaceWithNewChunk)
|
||||
})
|
||||
.once('end', () => {
|
||||
transform.push(replaceWithNewChunk)
|
||||
resolve()
|
||||
})
|
||||
replaceWith.resume()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
throw new Error("This shouldn't happen.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const foundMatch = async () => {
|
||||
await pushReplaceStr()
|
||||
matches++
|
||||
if (matches === limit) {
|
||||
doneReplacing = true
|
||||
}
|
||||
}
|
||||
|
||||
const transform = new Transform({
|
||||
async transform (chunk, encoding, callback) {
|
||||
if (doneReplacing) {
|
||||
callback(undefined, chunk)
|
||||
} else {
|
||||
// Convert to utf-8
|
||||
let chunkStr = stringDecoder.write(chunk)
|
||||
|
||||
/**
|
||||
* This is the end index of the string that we might replace later
|
||||
*/
|
||||
let keepEndIndex
|
||||
// Continue / complete / discard partial matches from previous chunks
|
||||
for (let i = 0; i < partialMatchesFromPrevChunk.length;) {
|
||||
const pastChunkMatchIndex = partialMatchesFromPrevChunk[i]
|
||||
const chunkStrEndIndex = searchStr.length - (unsureBuffer.length - pastChunkMatchIndex)
|
||||
const strToCheck = unsureBuffer.slice(pastChunkMatchIndex) + chunkStr.slice(0, chunkStrEndIndex)
|
||||
if (searchStr.startsWith(strToCheck)) {
|
||||
if (strToCheck.length >= searchStr.length) {
|
||||
// Match completed
|
||||
// Push the previous part of the string that was saved
|
||||
transform.push(unsureBuffer.slice(0, pastChunkMatchIndex))
|
||||
await foundMatch()
|
||||
unsureBuffer = ''
|
||||
partialMatchesFromPrevChunk = []
|
||||
chunkStr = chunkStr.slice(chunkStrEndIndex)
|
||||
} else {
|
||||
// Match continued
|
||||
keepEndIndex = keepEndIndex ?? 0
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
// Match discarded
|
||||
partialMatchesFromPrevChunk.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for completed / partial matches in the current chunk
|
||||
let chunkStrIndex = 0
|
||||
while (chunkStrIndex < chunkStr.length) {
|
||||
const strToCheck = chunkStr.slice(chunkStrIndex, chunkStrIndex + searchStr.length)
|
||||
if (searchStr.startsWith(strToCheck)) {
|
||||
if (strToCheck.length === searchStr.length) {
|
||||
// Match completed
|
||||
transform.push(chunkStr.slice(0, chunkStrIndex))
|
||||
await foundMatch()
|
||||
chunkStr = chunkStr.slice(chunkStrIndex + strToCheck.length)
|
||||
chunkStrIndex = 0
|
||||
} else {
|
||||
// Partial match
|
||||
partialMatchesFromPrevChunk.push(chunkStrIndex)
|
||||
keepEndIndex = keepEndIndex ?? chunkStrIndex
|
||||
chunkStrIndex++
|
||||
}
|
||||
} else {
|
||||
// No match
|
||||
if (keepEndIndex === undefined) {
|
||||
// Push the string out right away
|
||||
transform.push(chunkStr.slice(chunkStrIndex, chunkStrIndex + 1))
|
||||
chunkStr = chunkStr.slice(chunkStrIndex + 1)
|
||||
} else {
|
||||
// We are keeping the string
|
||||
chunkStrIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
// Save the part of the chunk that might be part of a match later
|
||||
unsureBuffer += chunkStr.slice(keepEndIndex)
|
||||
// This is needed
|
||||
callback()
|
||||
}
|
||||
},
|
||||
flush (callback) {
|
||||
// Release the unsureBuffer
|
||||
callback(undefined, unsureBuffer)
|
||||
}
|
||||
})
|
||||
|
||||
return transform
|
||||
}
|
||||
|
||||
export default replace
|
||||
Reference in New Issue
Block a user