Genesis
This commit is contained in:
126
parse-log.mjs
Normal file
126
parse-log.mjs
Normal file
@@ -0,0 +1,126 @@
|
||||
// CLI: parse ESC/POS .bin print jobs logged by the proxy.
|
||||
//
|
||||
// This is a *parser*: it decomposes the raw byte stream into the ESC/POS
|
||||
// command grammar — each control sequence recognized by its opcode with its
|
||||
// parsed parameters, and text as raw byte spans (decoded via the selected code
|
||||
// page). Nothing is interpreted into higher-level meaning (no "this is bold",
|
||||
// no line/style inference). Every byte is preserved and the stream can be
|
||||
// rebuilt via `serialize()` (verified by default).
|
||||
//
|
||||
// Usage:
|
||||
// node parse-log.mjs <file-or-dir> [options]
|
||||
//
|
||||
// Options:
|
||||
// --all treat each argument as a directory and parse all *.bin in it
|
||||
// --hex also print the raw bytes of every token
|
||||
// --text print only the decoded text spans (convenience view)
|
||||
// --image render raster bit images as ASCII art (downsampled)
|
||||
// --max-width n ASCII image width (default 80)
|
||||
// --no-verify skip the losslessness round-trip check
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { parse, serialize, renderText, renderImage, renderTokens, summarize } from "./parser.js";
|
||||
|
||||
function parseArgs(argv) {
|
||||
const opts = { files: [], mode: "tokens", hex: false, image: false, maxWidth: 80, all: false, verify: true };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === "--text") opts.mode = "text";
|
||||
else if (a === "--hex") opts.hex = true;
|
||||
else if (a === "--image") opts.image = true;
|
||||
else if (a === "--all") opts.all = true;
|
||||
else if (a === "--no-verify") opts.verify = false;
|
||||
else if (a === "--max-width") opts.maxWidth = Number(argv[++i]) || 80;
|
||||
else if (a.startsWith("--")) console.error(`[warn] unknown option ${a}`);
|
||||
else opts.files.push(a);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
function expandTargets(targets, all) {
|
||||
const out = [];
|
||||
for (const t of targets) {
|
||||
const stat = fs.statSync(t);
|
||||
if (stat.isDirectory()) {
|
||||
out.push(
|
||||
...fs
|
||||
.readdirSync(t)
|
||||
.filter((f) => f.endsWith(".bin"))
|
||||
.map((f) => path.join(t, f))
|
||||
.sort()
|
||||
);
|
||||
} else out.push(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const opts = parseArgs(process.argv.slice(2));
|
||||
if (opts.files.length === 0) {
|
||||
console.error(
|
||||
"usage: node parse-log.mjs <file-or-dir> [--hex|--text] [--image] [--all] [--no-verify]"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const files = expandTargets(opts.files, opts.all);
|
||||
let hadError = false;
|
||||
let allOk = true;
|
||||
|
||||
for (const f of files) {
|
||||
let buf;
|
||||
try {
|
||||
buf = fs.readFileSync(f);
|
||||
} catch (err) {
|
||||
console.error(`[error] cannot read ${f}: ${err.message}`);
|
||||
hadError = true;
|
||||
continue;
|
||||
}
|
||||
const events = parse(buf);
|
||||
const s = summarize(events);
|
||||
const name = path.basename(f);
|
||||
|
||||
let ok = true;
|
||||
if (opts.verify) {
|
||||
const out = serialize(events);
|
||||
ok = out.length === buf.length && out.equals(buf);
|
||||
if (!ok) {
|
||||
allOk = false;
|
||||
console.error(`[FAIL] ${name}: round-trip mismatch (${out.length} vs ${buf.length} bytes)`);
|
||||
}
|
||||
}
|
||||
const status = opts.verify ? (ok ? "OK" : "LOSSY") : "unverified";
|
||||
|
||||
if (opts.mode === "text") {
|
||||
console.log(`\n===== ${name} (${buf.length} bytes) =====\n`);
|
||||
console.log(renderText(events));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`\n===== ${name} (${buf.length} bytes) — round-trip: ${status} =====\n`);
|
||||
|
||||
console.log(renderTokens(events, { hex: opts.hex }));
|
||||
|
||||
if (opts.image) {
|
||||
for (const e of events) {
|
||||
if (e.type === "image") {
|
||||
console.log(`\n-- raster image ${e.width}x${e.height} --`);
|
||||
console.log(renderImage(e, opts.maxWidth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n[summary] ${s.commands} commands, ${s.images} image(s), ${s.cuts} cut(s), ` +
|
||||
`${s.text} text runs (${s.textBytes} bytes)`
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.verify && !allOk) {
|
||||
console.error("\n[error] at least one file was not losslessly reconstructed");
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(hadError ? 1 : 0);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user