boom-format 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ decode,
4
+ encode,
5
+ parseText,
6
+ stringify
7
+ } from "./chunk-5PQH6SJJ.js";
8
+
9
+ // src/cli.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ var VERSION = "1.0.0";
13
+ var BOOM_MAGIC = [66, 79, 79, 77];
14
+ function printHelp() {
15
+ console.log(`
16
+ BOOM CLI v${VERSION} - Binary Object Optimised Markup
17
+
18
+ Usage:
19
+ boom encode <input.json> [output] Encode JSON to BOOM format
20
+ boom decode <input.boom> [output.json] Decode BOOM to JSON
21
+ boom info <input.boom> Show info about a BOOM file
22
+ boom <input.json> Shorthand for encode
23
+
24
+ Output Formats:
25
+ --binary, -b Output BOOM binary format (default)
26
+ --text, -t Output BOOM text format (.boom.txt)
27
+ --json, -j Output JSON format (for decode)
28
+
29
+ Options:
30
+ --help, -h Show this help message
31
+ --version, -v Show version
32
+ --pretty, -p Pretty-print output (JSON and text)
33
+ --dict, -d Use shared dictionary (default: true)
34
+ --no-dict Disable shared dictionary
35
+
36
+ Examples:
37
+ boom encode data.json # Output: data.boom (binary)
38
+ boom encode data.json --text # Output: data.boom.txt (text)
39
+ boom encode data.json output.boom # Custom output file
40
+ boom decode data.boom # Output: data.json
41
+ boom decode data.boom --pretty # Pretty-printed JSON
42
+ boom decode data.boom.txt # Decode BOOM text
43
+ boom info data.boom # Show file info
44
+ cat data.json | boom encode - > out.boom # Stdin/stdout
45
+ cat data.boom | boom decode - # Decode from stdin
46
+
47
+ File Extensions:
48
+ .boom BOOM binary format
49
+ .boom.txt BOOM text format (human-readable)
50
+ .json JSON format
51
+ `);
52
+ }
53
+ function printVersion() {
54
+ console.log(`boom v${VERSION}`);
55
+ }
56
+ function parseArgs(args) {
57
+ const options = {
58
+ command: "help",
59
+ input: null,
60
+ output: null,
61
+ pretty: false,
62
+ useDict: true,
63
+ format: "binary"
64
+ };
65
+ const positional = [];
66
+ for (let i = 0; i < args.length; i++) {
67
+ const arg = args[i];
68
+ if (arg === void 0) continue;
69
+ if (arg === "--help" || arg === "-h") {
70
+ options.command = "help";
71
+ return options;
72
+ }
73
+ if (arg === "--version" || arg === "-v") {
74
+ options.command = "version";
75
+ return options;
76
+ }
77
+ if (arg === "--pretty" || arg === "-p") {
78
+ options.pretty = true;
79
+ continue;
80
+ }
81
+ if (arg === "--dict" || arg === "-d") {
82
+ options.useDict = true;
83
+ continue;
84
+ }
85
+ if (arg === "--no-dict") {
86
+ options.useDict = false;
87
+ continue;
88
+ }
89
+ if (arg === "--binary" || arg === "-b") {
90
+ options.format = "binary";
91
+ continue;
92
+ }
93
+ if (arg === "--text" || arg === "-t") {
94
+ options.format = "text";
95
+ continue;
96
+ }
97
+ if (arg === "--json" || arg === "-j") {
98
+ options.format = "json";
99
+ continue;
100
+ }
101
+ if (arg === "-") {
102
+ positional.push(arg);
103
+ } else if (arg.startsWith("-")) {
104
+ console.error(`Unknown option: ${arg}`);
105
+ process.exit(1);
106
+ } else {
107
+ positional.push(arg);
108
+ }
109
+ }
110
+ if (positional.length === 0) {
111
+ options.command = "help";
112
+ return options;
113
+ }
114
+ const cmd = positional[0];
115
+ if (!cmd) {
116
+ options.command = "help";
117
+ return options;
118
+ }
119
+ const cmdLower = cmd.toLowerCase();
120
+ if (cmdLower === "encode") {
121
+ options.command = "encode";
122
+ options.input = positional[1] ?? null;
123
+ options.output = positional[2] ?? null;
124
+ } else if (cmdLower === "decode") {
125
+ options.command = "decode";
126
+ options.input = positional[1] ?? null;
127
+ options.output = positional[2] ?? null;
128
+ } else if (cmdLower === "info") {
129
+ options.command = "info";
130
+ options.input = positional[1] ?? null;
131
+ } else if (cmdLower === "help") {
132
+ options.command = "help";
133
+ } else if (cmdLower === "version") {
134
+ options.command = "version";
135
+ } else {
136
+ const inputFile = positional[0];
137
+ if (inputFile) {
138
+ options.input = inputFile;
139
+ options.output = positional[1] ?? null;
140
+ if (inputFile.endsWith(".boom") || inputFile.endsWith(".boom.txt")) {
141
+ options.command = "decode";
142
+ } else {
143
+ options.command = "encode";
144
+ }
145
+ }
146
+ }
147
+ return options;
148
+ }
149
+ function readInput(inputPath) {
150
+ if (inputPath === "-") {
151
+ return fs.readFileSync(0);
152
+ }
153
+ return fs.readFileSync(inputPath);
154
+ }
155
+ function getOutputPath(inputPath, command, format) {
156
+ if (inputPath === "-") {
157
+ return "-";
158
+ }
159
+ let base;
160
+ const dir = path.dirname(inputPath);
161
+ if (inputPath.endsWith(".boom.txt")) {
162
+ base = path.basename(inputPath, ".boom.txt");
163
+ } else {
164
+ const ext = path.extname(inputPath);
165
+ base = path.basename(inputPath, ext);
166
+ }
167
+ if (command === "encode") {
168
+ if (format === "text") {
169
+ return path.join(dir, base + ".boom.txt");
170
+ }
171
+ return path.join(dir, base + ".boom");
172
+ } else {
173
+ return path.join(dir, base + ".json");
174
+ }
175
+ }
176
+ function writeOutput(outputPath, data) {
177
+ if (outputPath === "-") {
178
+ if (typeof data === "string") {
179
+ process.stdout.write(data);
180
+ } else {
181
+ process.stdout.write(data);
182
+ }
183
+ } else {
184
+ fs.writeFileSync(outputPath, data);
185
+ }
186
+ }
187
+ function formatBytes(bytes) {
188
+ if (bytes < 1024) return bytes + " B";
189
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
190
+ return (bytes / (1024 * 1024)).toFixed(2) + " MB";
191
+ }
192
+ async function runEncode(options) {
193
+ if (!options.input) {
194
+ console.error("Error: No input file specified");
195
+ process.exit(1);
196
+ }
197
+ const inputData = readInput(options.input);
198
+ const jsonData = JSON.parse(inputData.toString("utf-8"));
199
+ const outputPath = options.output || getOutputPath(options.input, "encode", options.format);
200
+ if (options.format === "text") {
201
+ const textData = stringify(jsonData, { compact: !options.pretty });
202
+ writeOutput(outputPath, textData + "\n");
203
+ if (outputPath !== "-") {
204
+ const savings = ((1 - textData.length / inputData.length) * 100).toFixed(1);
205
+ console.log(`Encoded: ${options.input} -> ${outputPath}`);
206
+ console.log(` JSON: ${formatBytes(inputData.length)}`);
207
+ console.log(` BOOM text: ${formatBytes(textData.length)} (${savings}% smaller)`);
208
+ }
209
+ } else {
210
+ const boomData = encode(jsonData, { enableInterning: options.useDict });
211
+ writeOutput(outputPath, Buffer.from(boomData));
212
+ if (outputPath !== "-") {
213
+ const savings = ((1 - boomData.length / inputData.length) * 100).toFixed(1);
214
+ console.log(`Encoded: ${options.input} -> ${outputPath}`);
215
+ console.log(` JSON: ${formatBytes(inputData.length)}`);
216
+ console.log(` BOOM binary: ${formatBytes(boomData.length)} (${savings}% smaller)`);
217
+ }
218
+ }
219
+ }
220
+ async function runDecode(options) {
221
+ if (!options.input) {
222
+ console.error("Error: No input file specified");
223
+ process.exit(1);
224
+ }
225
+ const inputData = readInput(options.input);
226
+ const isTextFormat = options.input.endsWith(".boom.txt") || options.input.endsWith(".txt") || !isBinaryFormat(inputData);
227
+ let jsonData;
228
+ let inputFormat;
229
+ if (isTextFormat) {
230
+ const textContent = inputData.toString("utf-8");
231
+ jsonData = parseText(textContent);
232
+ inputFormat = "BOOM text";
233
+ } else {
234
+ const boomArray = new Uint8Array(inputData);
235
+ if (boomArray.length < 6 || boomArray[0] !== BOOM_MAGIC[0] || boomArray[1] !== BOOM_MAGIC[1] || boomArray[2] !== BOOM_MAGIC[2] || boomArray[3] !== BOOM_MAGIC[3]) {
236
+ console.error("Error: Not a valid BOOM file (invalid magic header)");
237
+ process.exit(1);
238
+ }
239
+ jsonData = decode(boomArray);
240
+ inputFormat = "BOOM binary";
241
+ }
242
+ const jsonString = options.pretty ? JSON.stringify(jsonData, null, 2) : JSON.stringify(jsonData);
243
+ const outputPath = options.output || getOutputPath(options.input, "decode", options.format);
244
+ writeOutput(outputPath, jsonString + "\n");
245
+ if (outputPath !== "-") {
246
+ console.log(`Decoded: ${options.input} -> ${outputPath}`);
247
+ console.log(` ${inputFormat}: ${formatBytes(inputData.length)}`);
248
+ console.log(` JSON: ${formatBytes(jsonString.length)}`);
249
+ }
250
+ }
251
+ function isBinaryFormat(data) {
252
+ return data.length >= 4 && data[0] === BOOM_MAGIC[0] && data[1] === BOOM_MAGIC[1] && data[2] === BOOM_MAGIC[2] && data[3] === BOOM_MAGIC[3];
253
+ }
254
+ async function runInfo(options) {
255
+ if (!options.input) {
256
+ console.error("Error: No input file specified");
257
+ process.exit(1);
258
+ }
259
+ const inputData = readInput(options.input);
260
+ const boomArray = new Uint8Array(inputData);
261
+ if (boomArray.length < 6 || boomArray[0] !== BOOM_MAGIC[0] || boomArray[1] !== BOOM_MAGIC[1] || boomArray[2] !== BOOM_MAGIC[2] || boomArray[3] !== BOOM_MAGIC[3]) {
262
+ console.error("Error: Not a valid BOOM file (invalid magic header)");
263
+ process.exit(1);
264
+ }
265
+ const version = boomArray[4];
266
+ const flags = boomArray[5];
267
+ const usesDict = flags !== void 0 ? (flags & 1) !== 0 : false;
268
+ const jsonData = decode(boomArray);
269
+ const jsonString = JSON.stringify(jsonData);
270
+ const prettyJson = JSON.stringify(jsonData, null, 2);
271
+ const savings = ((1 - boomArray.length / jsonString.length) * 100).toFixed(1);
272
+ console.log(`BOOM File Info: ${options.input}`);
273
+ console.log(`${"\u2500".repeat(50)}`);
274
+ console.log(` Format version: ${version}`);
275
+ console.log(` Uses dictionary: ${usesDict ? "yes" : "no"}`);
276
+ console.log(` BOOM size: ${formatBytes(boomArray.length)}`);
277
+ console.log(` JSON size: ${formatBytes(jsonString.length)}`);
278
+ console.log(` Size reduction: ${savings}%`);
279
+ console.log(`${"\u2500".repeat(50)}`);
280
+ console.log(`Preview (first 500 chars):`);
281
+ console.log(prettyJson.slice(0, 500) + (prettyJson.length > 500 ? "..." : ""));
282
+ }
283
+ async function main() {
284
+ const args = process.argv.slice(2);
285
+ const options = parseArgs(args);
286
+ try {
287
+ switch (options.command) {
288
+ case "help":
289
+ printHelp();
290
+ break;
291
+ case "version":
292
+ printVersion();
293
+ break;
294
+ case "encode":
295
+ await runEncode(options);
296
+ break;
297
+ case "decode":
298
+ await runDecode(options);
299
+ break;
300
+ case "info":
301
+ await runInfo(options);
302
+ break;
303
+ }
304
+ } catch (error) {
305
+ if (error instanceof Error) {
306
+ console.error(`Error: ${error.message}`);
307
+ } else {
308
+ console.error("An unknown error occurred");
309
+ }
310
+ process.exit(1);
311
+ }
312
+ }
313
+ main();