forked from mikael-lovqvist/shell-utils
Initial commit
This commit is contained in:
119
ansi-tunc.mjs
Normal file
119
ansi-tunc.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
/* VIBE ALERT
|
||||
|
||||
This code was generated using OpenAI’s ChatGPT (GPT-5.2 Thinking, large language model).
|
||||
The output has not been independently reviewed, audited, or comprehensively validated,
|
||||
and has only been superficially tested. It should be considered experimental and not fully vetted for production use.
|
||||
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import readline from 'node:readline';
|
||||
|
||||
const ansiRegex = /\x1b\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[mK]?/g;
|
||||
|
||||
function tokenize(line) {
|
||||
const tokens = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
for (const match of line.matchAll(ansiRegex)) {
|
||||
const index = match.index;
|
||||
|
||||
if (index > lastIndex) {
|
||||
tokens.push({ type: 'text', value: line.slice(lastIndex, index) });
|
||||
}
|
||||
|
||||
tokens.push({ type: 'ansi', value: match[0] });
|
||||
lastIndex = index + match[0].length;
|
||||
}
|
||||
|
||||
if (lastIndex < line.length) {
|
||||
tokens.push({ type: 'text', value: line.slice(lastIndex) });
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function truncateLine(line, maxLength, tabSize=4) {
|
||||
let col = 0;
|
||||
let result = '';
|
||||
|
||||
const tokens = tokenize(line);
|
||||
|
||||
for (const token of tokens) {
|
||||
if (col >= maxLength) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.type === 'ansi') {
|
||||
result += token.value;
|
||||
} else {
|
||||
let i = 0;
|
||||
|
||||
while (i < token.value.length && col < maxLength) {
|
||||
const ch = token.value[i];
|
||||
|
||||
if (ch === '\t') {
|
||||
const w = tabSize - (col % tabSize);
|
||||
|
||||
if (col + w > maxLength) {
|
||||
i = token.value.length;
|
||||
} else {
|
||||
result += ' '.repeat(w);
|
||||
col += w;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
result += ch;
|
||||
col += 1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.replace(/\s+$/, '') + '\x1b[0m';
|
||||
}
|
||||
function parseArgs(argv) {
|
||||
let filename = '/dev/stdin';
|
||||
let length = 50;
|
||||
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
|
||||
if (arg === '-l' || arg === '--length') {
|
||||
const value = argv[i + 1];
|
||||
if (value) {
|
||||
length = parseInt(value, 10);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
filename = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return { filename, length };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { filename, length } = parseArgs(process.argv);
|
||||
|
||||
let inputStream;
|
||||
|
||||
if (filename === '/dev/stdin') {
|
||||
inputStream = process.stdin;
|
||||
} else {
|
||||
inputStream = fs.createReadStream(filename, { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: inputStream,
|
||||
crlfDelay: Infinity,
|
||||
});
|
||||
|
||||
for await (const line of rl) {
|
||||
const truncated = truncateLine(line, length);
|
||||
process.stdout.write(truncated + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
72
grep-mtime-sorter.mjs
Normal file
72
grep-mtime-sorter.mjs
Normal file
@@ -0,0 +1,72 @@
|
||||
/* VIBE ALERT
|
||||
|
||||
This code was generated using OpenAI’s ChatGPT (GPT-5.2 Thinking, large language model).
|
||||
The output has not been independently reviewed, audited, or comprehensively validated,
|
||||
and has only been superficially tested. It should be considered experimental and not fully vetted for production use.
|
||||
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import { stat } from 'node:fs/promises';
|
||||
|
||||
const lineRe = /^\x1b\[35m\x1b\[K(.*?)\x1b/;
|
||||
|
||||
|
||||
function isoFormat(timestamp) {
|
||||
if (timestamp) {
|
||||
const d = new Date(timestamp * 1000);
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
} else {
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
async function getMtime(line) {
|
||||
const m = lineRe.exec(line);
|
||||
if (m) {
|
||||
const filename = m[1];
|
||||
try {
|
||||
const s = await stat(filename);
|
||||
return s.mtimeMs / 1000;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
throw new Error('Malformed input: ANSI prefix not found');
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const input = await new Promise((resolve) => {
|
||||
let data = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', (chunk) => { data += chunk; });
|
||||
process.stdin.on('end', () => { resolve(data); });
|
||||
});
|
||||
|
||||
const lines = input.split(/(?<=\n)/);
|
||||
|
||||
const pairs = await Promise.all(
|
||||
lines.map(async (line, index) => {
|
||||
const t = await getMtime(line);
|
||||
return { t, index };
|
||||
})
|
||||
);
|
||||
|
||||
pairs.sort((a, b) => {
|
||||
if (a.t !== b.t) {
|
||||
return a.t - b.t;
|
||||
} else {
|
||||
return a.index - b.index;
|
||||
}
|
||||
});
|
||||
|
||||
for (const { t, index } of pairs) {
|
||||
const ts = isoFormat(t).padStart(16, ' ');
|
||||
process.stdout.write(`\x1b[94m${ts}\x1b[0m `);
|
||||
process.stdout.write(lines[index]);
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
10
package.json
Normal file
10
package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "ansi-tools",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"bin": {
|
||||
"ansi-trunc": "./ansi-trunc.mjs",
|
||||
"grep-mtime-sorter": "./grep-mtime-sorter.mjs"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user