toon-formatter 2.1.1 โ†’ 2.2.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/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # ๐Ÿš€ TOON Converter
2
2
 
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Node >=16.0.0](https://img.shields.io/badge/Node-%3E%3D16.0.0-blue.svg)](https://nodejs.org/en/download)
5
+ [![LLM APIs cost reduction](https://img.shields.io/badge/LLM%20APIs-Up%20to%2040%25%20cost%20reduction-orange)](https://toonformatter.net/)
6
+
3
7
  A lightweight library to convert between **TOON** (Token-Oriented Object Notation) and popular data formats (JSON, YAML, XML, CSV).
4
8
 
5
9
  **Reduce your LLM token costs by up to 40%** using the TOON format!
@@ -55,6 +59,64 @@ users[3]{id,name,active}:
55
59
 
56
60
  ---
57
61
 
62
+ ## ๐Ÿ’ป CLI Utility
63
+
64
+ **NEW in v2.1.0**: TOON Formatter now includes a powerful CLI utility for fast data conversion directly from your terminal!
65
+
66
+ ### Global Installation
67
+ To use the `toon-formatter` command anywhere:
68
+ ```bash
69
+ npm install -g toon-formatter
70
+ ```
71
+ Or run it instantly without installation using `npx`:
72
+ ```bash
73
+ npx toon-formatter --help
74
+ ```
75
+
76
+ ### Basic Usage
77
+ Convert files or piped data easily:
78
+ ```bash
79
+ # Convert JSON file to TOON
80
+ toon-formatter --from json --to toon -i data.json -o data.toon
81
+
82
+ # Piping data (JSON -> TOON)
83
+ echo '{"name": "Alice"}' | toon-formatter --from json --to toon
84
+
85
+ # Convert XML to JSON (prettified by default)
86
+ cat profile.xml | toon-formatter --from xml --to json
87
+ ```
88
+
89
+ ### Advanced Features
90
+ The CLI supports all library features, including validation and encryption:
91
+
92
+ ```bash
93
+ # Validate a TOON string
94
+ cat data.toon | toon-formatter --validate toon
95
+
96
+ # Encrypt data during conversion (XOR)
97
+ echo '{"secret": "data"}' | toon-formatter --from json --to toon --mode export --key "mykey" --algo xor
98
+
99
+ # Decrypt data (AES-256-GCM)
100
+ cat encrypted.txt | toon-formatter --from toon --to json --mode ingestion --key "your-32-byte-key" --algo aes-256-gcm
101
+ ```
102
+
103
+ ### CLI Options
104
+ | Flag | Short | Description |
105
+ |------|-------|-------------|
106
+ | `--from` | `-f` | Input format (json, yaml, xml, csv, toon) |
107
+ | `--to` | `-t` | Output format (json, yaml, xml, csv, toon) |
108
+ | `--input` | `-i` | Input file path (defaults to stdin) |
109
+ | `--output` | `-o` | Output file path (defaults to stdout) |
110
+ | `--validate` | `-v` | Validate the input format and exit |
111
+ | `--mode` | `-m` | Encryption mode (middleware, ingestion, export) |
112
+ | `--key` | `-k` | Encryption key |
113
+ | `--algo` | `-a` | Encryption algorithm (aes-256-gcm, xor, base64) |
114
+ | `--async` | | Use asynchronous conversion mode |
115
+ | `--no-parse` | | Skip parsing of objects (returns raw strings) |
116
+ | `--help` | `-h` | Show help information |
117
+
118
+ ---
119
+
58
120
  ## ๐Ÿš€ Quick Start
59
121
 
60
122
  ### Basic Usage (Synchronous)
@@ -1380,7 +1442,10 @@ fs.writeFileSync('config.yaml', improvedYaml);
1380
1442
 
1381
1443
  ## ๐Ÿงช Testing
1382
1444
 
1445
+ The library includes a comprehensive test suite with 150+ unit and integration tests.
1446
+
1383
1447
  ```bash
1448
+ # Run all tests (Unit, Integration, and CLI)
1384
1449
  npm test
1385
1450
  ```
1386
1451
 
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../src/cli.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-formatter",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "funding": {
5
5
  "type": "github",
6
6
  "url": "https://github.com/sponsors/ankitpal181"
@@ -8,6 +8,9 @@
8
8
  "description": "A powerful library to convert between TOON, JSON, YAML, XML, and CSV with end-to-end encryption. Includes Unified Converters for direct format-to-format translation and LLM token optimization.",
9
9
  "main": "src/index.js",
10
10
  "type": "module",
11
+ "bin": {
12
+ "toon-formatter": "./bin/toon-formatter.js"
13
+ },
11
14
  "scripts": {
12
15
  "test": "node --test test/*.test.js",
13
16
  "test:watch": "node --test --watch test/*.test.js"
package/src/cli.js ADDED
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import {
6
+ ToonConverter,
7
+ JsonConverter,
8
+ YamlConverter,
9
+ XmlConverter,
10
+ CsvConverter,
11
+ Encryptor
12
+ } from './index.js';
13
+
14
+ // Polyfill DOMParser for Node.js environments (required for XML conversion)
15
+ if (typeof DOMParser === 'undefined') {
16
+ try {
17
+ const { DOMParser: NodeDOMParser } = await import('xmldom');
18
+ global.DOMParser = NodeDOMParser;
19
+ } catch (e) {
20
+ // xmldom might not be available or other import error
21
+ }
22
+ }
23
+
24
+ async function readStdin() {
25
+ return new Promise((resolve, reject) => {
26
+ let data = '';
27
+ process.stdin.setEncoding('utf8');
28
+ process.stdin.on('readable', () => {
29
+ let chunk;
30
+ while ((chunk = process.stdin.read()) !== null) {
31
+ data += chunk;
32
+ }
33
+ });
34
+ process.stdin.on('end', () => resolve(data));
35
+ process.stdin.on('error', reject);
36
+ });
37
+ }
38
+
39
+ const showHelp = () => {
40
+ console.log(`
41
+ TOON Formatter CLI - Convert between TOON, JSON, YAML, XML, and CSV.
42
+
43
+ Usage:
44
+ toon-formatter --from <format> --to <format> [options]
45
+ toon-formatter --validate <format> [options]
46
+
47
+ Options:
48
+ --from <format> Source format (json, yaml, xml, csv, toon)
49
+ --to <format> Target format (json, yaml, xml, csv, toon)
50
+ -i, --input <file> Input file path (default: stdin)
51
+ -o, --output <file> Output file path (default: stdout)
52
+ --async Use asynchronous converters
53
+ -m, --mode <mode> Conversion mode (no_encryption, middleware, ingestion, export)
54
+ -k, --key <key> Encryption key
55
+ -a, --algo <algo> Encryption algorithm (aes-256-gcm, xor, base64)
56
+ --no-parse Return raw strings for applicable conversions
57
+ --validate <format> Validate the given format
58
+ -h, --help Show this help message
59
+
60
+ Examples:
61
+ echo '{"name": "Alice"}' | toon-formatter --from json --to toon
62
+ toon-formatter --from xml --to json -i data.xml -o data.json
63
+ toon-formatter --validate toon -i data.toon
64
+ `);
65
+ };
66
+
67
+ function parseArgs(args) {
68
+ const config = {
69
+ from: null,
70
+ to: null,
71
+ input: null,
72
+ output: null,
73
+ isAsync: false,
74
+ mode: 'no_encryption',
75
+ key: null,
76
+ algo: 'aes-256-gcm',
77
+ noParse: false,
78
+ validate: null
79
+ };
80
+
81
+ for (let i = 0; i < args.length; i++) {
82
+ const arg = args[i];
83
+ switch (arg) {
84
+ case '--from':
85
+ config.from = args[++i];
86
+ break;
87
+ case '--to':
88
+ config.to = args[++i];
89
+ break;
90
+ case '-i':
91
+ case '--input':
92
+ config.input = args[++i];
93
+ break;
94
+ case '-o':
95
+ case '--output':
96
+ config.output = args[++i];
97
+ break;
98
+ case '--async':
99
+ config.isAsync = true;
100
+ break;
101
+ case '-m':
102
+ case '--mode':
103
+ config.mode = args[++i];
104
+ break;
105
+ case '-k':
106
+ case '--key':
107
+ config.key = args[++i];
108
+ break;
109
+ case '-a':
110
+ case '--algo':
111
+ config.algo = args[++i];
112
+ break;
113
+ case '--no-parse':
114
+ config.noParse = true;
115
+ break;
116
+ case '--validate':
117
+ config.validate = args[++i];
118
+ break;
119
+ case '-h':
120
+ case '--help':
121
+ showHelp();
122
+ process.exit(0);
123
+ default:
124
+ if (arg.startsWith('-')) {
125
+ console.error(`Unknown option: ${arg}`);
126
+ process.exit(1);
127
+ }
128
+ }
129
+ }
130
+
131
+ return config;
132
+ }
133
+
134
+ async function run() {
135
+ const args = process.argv.slice(2);
136
+ if (args.length === 0) {
137
+ showHelp();
138
+ process.exit(0);
139
+ }
140
+
141
+ const config = parseArgs(args);
142
+
143
+ if (!config.validate && (!config.from || !config.to)) {
144
+ console.error("Error: --from and --to are required unless using --validate");
145
+ process.exit(1);
146
+ }
147
+
148
+ let inputData = '';
149
+ if (config.input) {
150
+ try {
151
+ inputData = fs.readFileSync(path.resolve(config.input), 'utf8');
152
+ } catch (err) {
153
+ console.error(`Error reading input file: ${err.message}`);
154
+ process.exit(1);
155
+ }
156
+ } else {
157
+ inputData = await readStdin();
158
+ }
159
+
160
+ if (!inputData && !config.validate) {
161
+ console.error("Error: No input data provided");
162
+ process.exit(1);
163
+ }
164
+
165
+ let encryptor = null;
166
+ if (config.key || config.algo === 'base64') {
167
+ try {
168
+ encryptor = new Encryptor(config.key, config.algo);
169
+ } catch (err) {
170
+ console.error(`Error initializing encryptor: ${err.message}`);
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ try {
176
+ let result;
177
+ if (config.validate) {
178
+ result = ToonConverter.validate(inputData);
179
+ if (config.output) {
180
+ fs.writeFileSync(path.resolve(config.output), JSON.stringify(result, null, 2));
181
+ } else {
182
+ console.log(JSON.stringify(result, null, 2));
183
+ }
184
+ return;
185
+ }
186
+
187
+ const options = {
188
+ conversionMode: config.mode,
189
+ returnJson: !config.noParse
190
+ };
191
+
192
+ result = await handleConversion(config, inputData, encryptor);
193
+
194
+ if (typeof result === 'object' && result !== null) {
195
+ result = JSON.stringify(result, null, 2);
196
+ } else if (typeof result === 'string' && config.to === 'json' && !config.noParse) {
197
+ try {
198
+ const parsed = JSON.parse(result);
199
+ result = JSON.stringify(parsed, null, 2);
200
+ } catch (e) {
201
+ // Not a valid JSON string, leave as is
202
+ }
203
+ }
204
+
205
+ if (config.output) {
206
+ fs.writeFileSync(path.resolve(config.output), result);
207
+ } else {
208
+ process.stdout.write(result + '\n');
209
+ }
210
+
211
+ } catch (err) {
212
+ console.error(`Conversion error: ${err.message}`);
213
+ process.exit(1);
214
+ }
215
+ }
216
+
217
+ async function handleConversion(config, data, encryptor) {
218
+ const { from, to, isAsync, mode, noParse } = config;
219
+
220
+ // Use instance if encryption is involved
221
+ const toonConv = new ToonConverter(encryptor);
222
+ const jsonConv = new JsonConverter(encryptor);
223
+ const yamlConv = new YamlConverter(encryptor);
224
+ const xmlConv = new XmlConverter(encryptor);
225
+ const csvConv = new CsvConverter(encryptor);
226
+
227
+ const options = { conversionMode: mode, returnJson: !noParse };
228
+
229
+ if (from === 'toon') {
230
+ if (to === 'json') return isAsync ? toonConv.toJsonAsync(data, options) : toonConv.toJson(data, options);
231
+ if (to === 'yaml') return isAsync ? toonConv.toYamlAsync(data, options) : toonConv.toYaml(data, options);
232
+ if (to === 'xml') return isAsync ? toonConv.toXmlAsync(data, options) : toonConv.toXml(data, options);
233
+ if (to === 'csv') return isAsync ? toonConv.toCsvAsync(data, options) : toonConv.toCsv(data, options);
234
+ }
235
+
236
+ if (to === 'toon') {
237
+ if (from === 'json') return isAsync ? toonConv.fromJsonAsync(data, options) : toonConv.fromJson(data, options);
238
+ if (from === 'yaml') return isAsync ? toonConv.fromYamlAsync(data, options) : toonConv.fromYaml(data, options);
239
+ if (from === 'xml') return isAsync ? toonConv.fromXmlAsync(data, options) : toonConv.fromXml(data, options);
240
+ if (from === 'csv') return isAsync ? toonConv.fromCsvAsync(data, options) : toonConv.fromCsv(data, options);
241
+ }
242
+
243
+ // Unified Converters for direct translation
244
+ if (from === 'json') {
245
+ if (to === 'xml') return isAsync ? jsonConv.toXmlAsync(data, options) : jsonConv.toXml(data, options);
246
+ if (to === 'csv') return isAsync ? jsonConv.toCsvAsync(data, options) : jsonConv.toCsv(data, options);
247
+ if (to === 'yaml') return isAsync ? jsonConv.toYamlAsync(data, options) : jsonConv.toYaml(data, options);
248
+ }
249
+
250
+ if (from === 'yaml') {
251
+ if (to === 'json') return isAsync ? yamlConv.toJsonAsync(data, options) : yamlConv.toJson(data, options);
252
+ if (to === 'xml') return isAsync ? yamlConv.toXmlAsync(data, options) : yamlConv.toXml(data, options);
253
+ if (to === 'csv') return isAsync ? yamlConv.toCsvAsync(data, options) : yamlConv.toCsv(data, options);
254
+ }
255
+
256
+ if (from === 'xml') {
257
+ if (to === 'json') return isAsync ? xmlConv.toJsonAsync(data, options) : xmlConv.toJson(data, options);
258
+ if (to === 'csv') return isAsync ? xmlConv.toCsvAsync(data, options) : xmlConv.toCsv(data, options);
259
+ if (to === 'yaml') return isAsync ? xmlConv.toYamlAsync(data, options) : xmlConv.toYaml(data, options);
260
+ }
261
+
262
+ if (from === 'csv') {
263
+ if (to === 'json') return isAsync ? csvConv.toJsonAsync(data, options) : csvConv.toJson(data, options);
264
+ if (to === 'xml') return isAsync ? csvConv.toXmlAsync(data, options) : csvConv.toXml(data, options);
265
+ if (to === 'yaml') return isAsync ? csvConv.toYamlAsync(data, options) : csvConv.toYaml(data, options);
266
+ }
267
+
268
+ throw new Error(`Unsupported conversion from ${from} to ${to}`);
269
+ }
270
+
271
+ run();