Comparing version 2.2.4 to 2.2.5
146
lib/tail.js
@@ -51,35 +51,145 @@ let events = require(`events`) | ||
let cursor; | ||
this.logger.info(`fromBeginning: ${fromBeginning}`); | ||
let startingCursor; | ||
if (fromBeginning) { | ||
startingCursor = 0; | ||
cursor = 0; | ||
} else if (this.nLines <= 0) { | ||
cursor = 0; | ||
} else if (this.nLines !== undefined) { | ||
const data = fs.readFileSync(this.filename, { | ||
flag: 'r', | ||
encoding: this.encoding | ||
}); | ||
const tokens = data.split(this.separator); | ||
const dropLastToken = (tokens[tokens.length - 1] === '') ? 1 : 0;//if the file ends with empty line ignore line NL | ||
if (tokens.length - this.nLines - dropLastToken <= 0) { | ||
//nLines is bigger than avaiable tokens: tail from the begin | ||
startingCursor = 0; | ||
} else { | ||
const match = data.match(new RegExp(`(?:[^\r\n]*[\r]{0,1}\n){${tokens.length - this.nLines - dropLastToken}}`)); | ||
startingCursor = (match && match.length) ? Buffer.byteLength(match[0], this.encoding) : this.latestPosition(); | ||
} | ||
cursor = this.getPositionAtNthLine(this.nLines); | ||
} else { | ||
startingCursor = this.latestPosition(); | ||
cursor = this.latestPosition(); | ||
} | ||
if (startingCursor === undefined) throw new Error("Tail can't initialize."); | ||
if (cursor === undefined) throw new Error("Tail can't initialize."); | ||
const flush = fromBeginning || (this.nLines != undefined); | ||
try { | ||
this.watch(startingCursor, flush); | ||
this.watch(cursor, flush); | ||
} catch (err) { | ||
this.logger.error(`watch for ${this.filename} failed: ${err}`); | ||
this.emit("error", `watch for ${this.filename} failed: ${err}`); | ||
} | ||
} | ||
/** | ||
* Grabs the index of the last line of text in the format /.*(\n)?/. | ||
* Returns null if a full line can not be found. | ||
* @param {string} text | ||
* @returns {number | null} | ||
*/ | ||
getIndexOfLastLine(text) { | ||
/** | ||
* Helper function get the last match as string | ||
* @param {string} haystack | ||
* @param {string | RegExp} needle | ||
* @returns {string | undefined} | ||
*/ | ||
const getLastMatch = (haystack, needle) => { | ||
const matches = haystack.match(needle); | ||
if (matches === null) { | ||
return; | ||
} | ||
return matches[matches.length - 1]; | ||
}; | ||
const endSep = getLastMatch(text, this.separator); | ||
if (!endSep) return null; | ||
const endSepIndex = text.lastIndexOf(endSep); | ||
let lastLine; | ||
if (text.endsWith(endSep)) { | ||
// If the text ends with a separator, look back further to find the next | ||
// separator to complete the line | ||
const trimmed = text.substring(0, endSepIndex); | ||
const startSep = getLastMatch(trimmed, this.separator); | ||
// If there isn't another separator, the line isn't complete so | ||
// so return null to get more data | ||
if (!startSep) { | ||
return null; | ||
} | ||
const startSepIndex = trimmed.lastIndexOf(startSep); | ||
// Exclude the starting separator, include the ending separator | ||
lastLine = text.substring( | ||
startSepIndex + startSep.length, | ||
endSepIndex + endSep.length | ||
); | ||
} else { | ||
// If the text does not end with a separator, grab everything after | ||
// the last separator | ||
lastLine = text.substring(endSepIndex + endSep.length); | ||
} | ||
return text.lastIndexOf(lastLine); | ||
} | ||
/** | ||
* Returns the position of the start of the `nLines`th line from the bottom. | ||
* Returns 0 if `nLines` is greater than the total number of lines in the file. | ||
* @param {number} nLines | ||
* @returns {number} | ||
*/ | ||
getPositionAtNthLine(nLines) { | ||
const { size } = fs.statSync(this.filename); | ||
const fd = fs.openSync(this.filename, 'r'); | ||
// Start from the end of the file and work backwards in specific chunks | ||
let currentReadPosition = size; | ||
const chunkSizeBytes = Math.min(1024, size); | ||
const lineBytes = []; | ||
let remaining = ''; | ||
while (lineBytes.length < nLines) { | ||
// Shift the current read position backward to the amount we're about to read | ||
currentReadPosition -= chunkSizeBytes; | ||
// If negative, we've reached the beginning of the file and we should stop and return 0, starting the | ||
// stream at the beginning. | ||
if (currentReadPosition < 0) { | ||
return 0; | ||
} | ||
// Read a chunk of the file and prepend it to the working buffer | ||
const buffer = Buffer.alloc(chunkSizeBytes); | ||
const bytesRead = fs.readSync(fd, buffer, | ||
0, // position in buffer to write to | ||
chunkSizeBytes, // number of bytes to read | ||
currentReadPosition // position in file to read from | ||
); | ||
// .subarray returns Uint8Array in node versions < 16.x and Buffer | ||
// in versions >= 16.x. To support both, allocate a new buffer with | ||
// Buffer.from which accepts both types | ||
const readArray = buffer.subarray(0, bytesRead); | ||
remaining = Buffer.from(readArray).toString(this.encoding) + remaining; | ||
let index = this.getIndexOfLastLine(remaining); | ||
while (index !== null && lineBytes.length < nLines) { | ||
const line = remaining.substring(index); | ||
lineBytes.push(Buffer.byteLength(line)); | ||
remaining = remaining.substring(0, index); | ||
index = this.getIndexOfLastLine(remaining); | ||
} | ||
} | ||
fs.closeSync(fd); | ||
return size - lineBytes.reduce((acc, cur) => acc + cur, 0) | ||
} | ||
latestPosition() { | ||
@@ -86,0 +196,0 @@ try { |
@@ -17,3 +17,3 @@ { | ||
], | ||
"version": "2.2.4", | ||
"version": "2.2.5", | ||
"homepage": "https://www.lucagrulla.com/node-tail", | ||
@@ -38,5 +38,5 @@ "repository": { | ||
"chai": "4.x", | ||
"mocha": "9.x", | ||
"mocha": "10.x", | ||
"nyc": "^15.1.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17898
292
4