Files
tetsuya-kitayama b627224308 init
2026-05-18 10:19:19 +09:00

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();