cbor-edn
Version:
Parse CBOR Extended Diagnostic Notation as defined by [draft-ietf-cbor-edn-literals-16](https://www.ietf.org/archive/id/draft-ietf-cbor-edn-literals-16.html) and some CBOR working group discussions.
159 lines (148 loc) • 3.87 kB
JavaScript
import {DiagnosticSizes, comment, decode, diagnose} from 'cbor2';
import {getRanges, u8toHex} from 'cbor2/utils';
import {ByteTree} from '../lib/byteTree.js';
import fs from 'node:fs';
import {parseEDN} from '../lib/index.js';
import util from 'node:util';
/** @type {import('node:util').ParseArgsConfig.options} */
const options = {
always: {
short: 'a',
type: 'boolean',
default: false,
description: 'Always add encoding indicators',
},
never: {
short: 'n',
type: 'boolean',
default: false,
description: 'Never add encoding indicators',
},
file: {
short: 'f',
type: 'string',
multiple: true,
default: ['-'],
description: 'File to read from if no positional args. "-" for stdin.',
},
ranges: {
short: 'r',
type: 'boolean',
description: 'Output ranges',
},
startRule: {
short: 's',
type: 'string',
default: 'one_item',
description: 'Start at this rule for parsing, instead of "seq".',
},
validateUTF8: {
short: 'V',
type: 'boolean',
description: 'Validate UTF8 in strings',
},
help: {
short: 'h',
type: 'boolean',
description: 'Show help for this command',
},
verbose: {
short: 'v',
type: 'boolean',
description: 'Verbose output from errors',
},
};
const opts = util.parseArgs({
strict: true,
allowPositionals: true,
options,
});
if (opts.values.help) {
// Janky help
console.error('edn.js [options] [EDN string]\n');
for (const [k, v] of Object.entries(options)) {
const opt = `--${v.short}, --${k}`.padEnd(17, ' ');
console.error(`${opt}${v.description}`);
}
process.exit(64);
}
const colors = process.stdout.isTTY;
let diagnosticSizes = DiagnosticSizes.PREFERRED;
if (opts.values.never) {
if (opts.values.always) {
console.error('--never and --always are mutually-exclusive');
process.exit(1);
}
diagnosticSizes = DiagnosticSizes.NEVER;
}
if (opts.values.always) {
diagnosticSizes = DiagnosticSizes.ALWAYS;
}
function decodeU8(obj) {
if (typeof obj === 'object') {
if (obj instanceof Uint8Array) {
return `0x${u8toHex(obj)}`;
}
if (Array.isArray(obj)) {
return obj.map(o => decodeU8(o));
}
if (obj instanceof Map) {
const ents = obj.entries();
return new Map(ents.map(([k, v]) => [k, decodeU8(v)]));
}
if (obj instanceof ByteTree) {
return obj;
}
const ents = Object.entries(obj);
return Object.fromEntries(ents.map(([k, v]) => [k, decodeU8(v)]));
}
return obj;
}
const inputs = (opts.positionals.length < 1) ?
opts.values.file.map(f => fs.readFileSync((f === '-') ? 0 : f, 'utf8')) :
opts.positionals;
try {
let i = 0;
for (const inp of inputs) {
const bytes = parseEDN(inp, {
startRule: opts.values.startRule,
grammarSource: (opts.positionals.length < 1) ? opts.values.file[i++] : `argument#${++i}`,
validateUTF8: opts.values.validateUTF8,
});
if (bytes instanceof Uint8Array) {
if (bytes.length > 0) {
console.log('bytes:', u8toHex(bytes));
if (opts.values.ranges) {
console.log(getRanges(bytes));
}
console.log(comment(bytes));
const js = decode(bytes);
console.log('js:', util.inspect(js, {
depth: Infinity,
colors,
}));
console.log(
'diagonstic recreated from js:',
util.inspect(diagnose(bytes, {
diagnosticSizes,
}), {colors})
);
} else {
console.log('no bytes generated');
}
} else {
console.log('js:', util.inspect(decodeU8(bytes), {
depth: Infinity,
colors,
}));
}
console.log();
}
} catch (e) {
if (opts.values.verbose) {
console.log(e);
} else {
console.log(e.message);
}
}