127 lines
4.0 KiB
JavaScript
127 lines
4.0 KiB
JavaScript
// 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();
|