145 lines
3.9 KiB
JavaScript
145 lines
3.9 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
function usage() {
|
|
console.log('Usage: node sort-logs.js <file-or-directory> [--write]');
|
|
console.log(' <file-or-directory> : .txt file or directory containing .txt logs');
|
|
console.log(' --write : rewrite files in chronological order');
|
|
}
|
|
|
|
function parseDateFromFileName(filePath) {
|
|
const match = path.basename(filePath).match(/(\d{4})_(\d{2})_(\d{2})/);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
year: Number(match[1]),
|
|
month: Number(match[2]),
|
|
day: Number(match[3]),
|
|
};
|
|
}
|
|
|
|
function parseTimestamp(line, fileDate) {
|
|
const match = line.match(/\((?:(\d{4})\/)?(\d{1,2})\/(\d{1,2})\s+(\d{1,2}):(\d{2}):(\d{2})\)\s*$/);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
|
|
const year = match[1] ? Number(match[1]) : fileDate?.year;
|
|
if (!year) {
|
|
return null;
|
|
}
|
|
|
|
const month = Number(match[2]);
|
|
const day = Number(match[3]);
|
|
const hour = Number(match[4]);
|
|
const minute = Number(match[5]);
|
|
const second = Number(match[6]);
|
|
|
|
return new Date(year, month - 1, day, hour, minute, second).getTime();
|
|
}
|
|
|
|
function getTargetFiles(targetPath) {
|
|
const resolvedPath = path.resolve(targetPath);
|
|
const stat = fs.statSync(resolvedPath);
|
|
|
|
if (stat.isFile()) {
|
|
return [resolvedPath];
|
|
}
|
|
|
|
if (stat.isDirectory()) {
|
|
return fs.readdirSync(resolvedPath)
|
|
.filter((entry) => entry.endsWith('.txt'))
|
|
.map((entry) => path.join(resolvedPath, entry))
|
|
.sort();
|
|
}
|
|
|
|
throw new Error(`Unsupported target: ${resolvedPath}`);
|
|
}
|
|
|
|
function sortFile(filePath, writeBack) {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const hasTrailingNewline = content.endsWith('\n');
|
|
const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
|
|
const fileDate = parseDateFromFileName(filePath);
|
|
|
|
const decorated = lines.map((line, index) => ({
|
|
line,
|
|
index,
|
|
timestamp: parseTimestamp(line, fileDate),
|
|
}));
|
|
|
|
const sortable = decorated.filter((entry) => entry.timestamp !== null);
|
|
if (sortable.length === 0) {
|
|
return { changed: false, reason: 'no timestamps found' };
|
|
}
|
|
|
|
const sorted = [...decorated].sort((left, right) => {
|
|
if (left.timestamp === null && right.timestamp === null) {
|
|
return left.index - right.index;
|
|
}
|
|
if (left.timestamp === null) {
|
|
return 1;
|
|
}
|
|
if (right.timestamp === null) {
|
|
return -1;
|
|
}
|
|
if (left.timestamp !== right.timestamp) {
|
|
return left.timestamp - right.timestamp;
|
|
}
|
|
return left.index - right.index;
|
|
});
|
|
|
|
const sortedContent = sorted.map((entry) => entry.line).join('\n') + (hasTrailingNewline ? '\n' : '');
|
|
if (sortedContent === content) {
|
|
return { changed: false, reason: 'already sorted' };
|
|
}
|
|
|
|
if (writeBack) {
|
|
fs.writeFileSync(filePath, sortedContent, 'utf8');
|
|
}
|
|
|
|
return {
|
|
changed: true,
|
|
reason: writeBack ? 'rewritten' : 'would rewrite',
|
|
firstBefore: decorated[0]?.line ?? '',
|
|
firstAfter: sorted[0]?.line ?? '',
|
|
lastBefore: decorated[decorated.length - 1]?.line ?? '',
|
|
lastAfter: sorted[sorted.length - 1]?.line ?? '',
|
|
};
|
|
}
|
|
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
const writeBack = args.includes('--write');
|
|
const target = args.find((arg) => !arg.startsWith('-'));
|
|
|
|
if (!target) {
|
|
usage();
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
|
|
let files;
|
|
try {
|
|
files = getTargetFiles(target);
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
|
|
let changedCount = 0;
|
|
for (const filePath of files) {
|
|
const result = sortFile(filePath, writeBack);
|
|
if (result.changed) {
|
|
changedCount += 1;
|
|
}
|
|
console.log(`${path.relative(process.cwd(), filePath)}: ${result.reason}`);
|
|
}
|
|
|
|
console.log(`${files.length} file(s) checked, ${changedCount} file(s) ${writeBack ? 'rewritten' : 'need sorting'}.`);
|
|
}
|
|
|
|
main(); |