toon-formatter 2.1.1 โ 2.2.1
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 +65 -0
- package/bin/toon-formatter.js +3 -0
- package/package.json +5 -2
- package/src/cli.js +271 -0
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# ๐ TOON Converter
|
|
2
2
|
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/en/download)
|
|
5
|
+
[](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.2.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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toon-formatter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
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"
|
|
@@ -69,4 +72,4 @@
|
|
|
69
72
|
"./xml-converter": "./src/xml_formatter/index.js",
|
|
70
73
|
"./csv-converter": "./src/csv_formatter/index.js"
|
|
71
74
|
}
|
|
72
|
-
}
|
|
75
|
+
}
|
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();
|