toon-formatter 1.0.2 → 1.1.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/ENHANCEMENTS.md +124 -0
- package/README.md +398 -54
- package/package.json +1 -1
- package/src/csv.js +66 -24
- package/src/index.js +103 -22
- package/src/json.js +65 -25
- package/src/utils.js +155 -0
- package/src/validator.js +73 -11
- package/src/xml.js +86 -34
- package/src/yaml.js +27 -8
- package/test/basic.test.js +17 -17
- package/test/converters.test.js +9 -9
package/src/validator.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TOON String Validator
|
|
2
|
+
* TOON String Validator (Enhanced)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { splitByDelimiter } from './utils.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Validates a TOON string for syntax and structural correctness
|
|
8
|
+
* Validates a TOON string for syntax and structural correctness (Synchronous)
|
|
9
9
|
* @param {string} toonString - TOON string to validate
|
|
10
10
|
* @returns {{isValid: boolean, error: string|null}} Validation result
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
12
|
+
export function validateToonStringSync(toonString) {
|
|
13
13
|
if (!toonString || typeof toonString !== 'string') {
|
|
14
14
|
return { isValid: false, error: 'Input must be a non-empty string.' };
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const lines = toonString.split('\n');
|
|
18
|
-
// Stack of contexts: { indent, type: 'root'|'object'|'array', expected?, count? }
|
|
18
|
+
// Stack of contexts: { indent, type: 'root'|'object'|'array', expected?, count?, isTabular? }
|
|
19
19
|
const contextStack = [{ indent: 0, type: 'root', count: 0 }];
|
|
20
20
|
let lineNumber = 0;
|
|
21
21
|
|
|
22
22
|
// Regex Definitions (based on TOON Rules)
|
|
23
23
|
const REGEX = {
|
|
24
24
|
mapKey: /^[^:\[]+:\s*$/,
|
|
25
|
-
arrayKey: /^[
|
|
25
|
+
arrayKey: /^[^:\[]+\[(\d+)([\t|])?\](?:\{[^}]+\})?:\s*(.*)$/, // Capture N, delimiter, content
|
|
26
26
|
rootArray: /^\[(\d+)([\t|])?\](?:\{[^}]+\})?:\s*(.*)$/, // Capture N, delimiter, content
|
|
27
27
|
listItem: /^\-.*/,
|
|
28
28
|
listItemEmpty: /^\-\s*$/,
|
|
@@ -54,9 +54,30 @@ export function validateToonString(toonString) {
|
|
|
54
54
|
|
|
55
55
|
const trimmedLine = line.trim();
|
|
56
56
|
const currentIndent = rawLine.search(/\S|$/);
|
|
57
|
-
|
|
57
|
+
let currentContext = contextStack[contextStack.length - 1];
|
|
58
58
|
const requiredIndent = currentContext.indent;
|
|
59
59
|
|
|
60
|
+
// --- Strict Check for Empty Blocks ---
|
|
61
|
+
// If the previous line opened a block (like an array) but we didn't indent,
|
|
62
|
+
// and it wasn't an inline array, then it's an empty block.
|
|
63
|
+
// If the array expects items (size > 0), this is an error.
|
|
64
|
+
if (lineNumber > 1) {
|
|
65
|
+
const prevLineRaw = lines[lineNumber - 2];
|
|
66
|
+
const prevLineTrimmed = prevLineRaw.trim();
|
|
67
|
+
const arrMatch = prevLineTrimmed.match(REGEX.arrayKey) || prevLineTrimmed.match(REGEX.rootArray);
|
|
68
|
+
|
|
69
|
+
if (arrMatch && currentIndent <= requiredIndent) {
|
|
70
|
+
// It was an array declaration, and we are NOT inside it (no indent increase).
|
|
71
|
+
const size = parseInt(arrMatch[1], 10);
|
|
72
|
+
const content = arrMatch[3];
|
|
73
|
+
|
|
74
|
+
// If no inline content and size > 0, it's a missing block.
|
|
75
|
+
if ((!content || content.trim() === '') && size > 0) {
|
|
76
|
+
return { isValid: false, error: `L${lineNumber - 1}: Array declared with size ${size} but has no items (expected indented block).` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
// --- Inline Array Validation ---
|
|
61
82
|
let arrayMatch = trimmedLine.match(REGEX.arrayKey) || trimmedLine.match(REGEX.rootArray);
|
|
62
83
|
if (arrayMatch) {
|
|
@@ -78,6 +99,7 @@ export function validateToonString(toonString) {
|
|
|
78
99
|
}
|
|
79
100
|
} else {
|
|
80
101
|
// Block Array start.
|
|
102
|
+
// If it's Root Array at indent 0, update root context.
|
|
81
103
|
if (trimmedLine.match(REGEX.rootArray) && contextStack.length === 1) {
|
|
82
104
|
contextStack[0].type = 'array';
|
|
83
105
|
contextStack[0].expected = size;
|
|
@@ -88,14 +110,17 @@ export function validateToonString(toonString) {
|
|
|
88
110
|
|
|
89
111
|
// --- State Management (Tabular) ---
|
|
90
112
|
if (isInsideTabularArray) {
|
|
113
|
+
// For tabular arrays, accept any indent >= root indent after the header
|
|
91
114
|
const rootContext = contextStack[0];
|
|
92
115
|
if (currentIndent >= rootContext.indent || (rootContext.indent === 0 && currentIndent > 0)) {
|
|
93
116
|
if (trimmedLine.includes(':') && !trimmedLine.startsWith('"')) {
|
|
94
117
|
return { isValid: false, error: `L${lineNumber}: Tabular rows cannot contain a colon.` };
|
|
95
118
|
}
|
|
119
|
+
// Count tabular row as item
|
|
96
120
|
if (rootContext.type === 'array') {
|
|
97
121
|
rootContext.count++;
|
|
98
122
|
}
|
|
123
|
+
// Update root indent if this is first data row
|
|
99
124
|
if (rootContext.indent === 0) {
|
|
100
125
|
rootContext.indent = currentIndent;
|
|
101
126
|
}
|
|
@@ -113,30 +138,43 @@ export function validateToonString(toonString) {
|
|
|
113
138
|
return { isValid: false, error: `L${lineNumber}: Indentation error.` };
|
|
114
139
|
}
|
|
115
140
|
|
|
141
|
+
// Determine context type
|
|
116
142
|
let newContext = { indent: currentIndent, type: 'object' };
|
|
117
143
|
|
|
118
144
|
const prevArrayMatch = prevLineTrimmed.match(REGEX.arrayKey) || prevLineTrimmed.match(REGEX.rootArray);
|
|
119
145
|
if (prevArrayMatch) {
|
|
146
|
+
// Check if this is a root array that was already initialized
|
|
120
147
|
const isRootArrayAlreadySet = prevLineTrimmed.match(REGEX.rootArray) &&
|
|
121
148
|
contextStack.length === 1 &&
|
|
122
149
|
contextStack[0].type === 'array';
|
|
123
150
|
|
|
151
|
+
const isTabular = prevLineTrimmed.includes('{') && prevLineTrimmed.includes('}');
|
|
152
|
+
|
|
124
153
|
if (!isRootArrayAlreadySet) {
|
|
154
|
+
// Create new array context for non-root arrays
|
|
125
155
|
const size = parseInt(prevArrayMatch[1], 10);
|
|
126
|
-
newContext = { indent: currentIndent, type: 'array', expected: size, count: 0 };
|
|
156
|
+
newContext = { indent: currentIndent, type: 'array', expected: size, count: 0, isTabular: isTabular };
|
|
127
157
|
contextStack.push(newContext);
|
|
158
|
+
} else {
|
|
159
|
+
// For root arrays, update the existing context
|
|
160
|
+
contextStack[0].isTabular = isTabular;
|
|
128
161
|
}
|
|
129
162
|
} else {
|
|
130
163
|
contextStack.push(newContext);
|
|
131
164
|
}
|
|
132
165
|
|
|
166
|
+
// Update root array indent if this is the first indented item
|
|
133
167
|
if (contextStack.length === 1 && contextStack[0].type === 'array' && contextStack[0].indent === 0) {
|
|
134
168
|
contextStack[0].indent = currentIndent;
|
|
135
169
|
}
|
|
136
170
|
|
|
171
|
+
// Count the current line if it's a list item in an array
|
|
137
172
|
const targetContext = contextStack[contextStack.length - 1];
|
|
138
173
|
if (targetContext.type === 'array') {
|
|
139
|
-
if (
|
|
174
|
+
if (targetContext.isTabular) {
|
|
175
|
+
// Tabular items don't need dash
|
|
176
|
+
targetContext.count++;
|
|
177
|
+
} else if (trimmedLine.match(REGEX.listItem)) {
|
|
140
178
|
targetContext.count++;
|
|
141
179
|
}
|
|
142
180
|
}
|
|
@@ -145,9 +183,11 @@ export function validateToonString(toonString) {
|
|
|
145
183
|
// Un-indentation
|
|
146
184
|
let foundMatch = false;
|
|
147
185
|
|
|
186
|
+
// Pop and Validate
|
|
148
187
|
while (contextStack.length > 1) {
|
|
149
188
|
const popped = contextStack.pop();
|
|
150
189
|
|
|
190
|
+
// Validate Array Size on Close
|
|
151
191
|
if (popped.type === 'array') {
|
|
152
192
|
if (popped.count !== popped.expected) {
|
|
153
193
|
return { isValid: false, error: `Array size mismatch. Declared ${popped.expected}, found ${popped.count} items (ending around L${lineNumber}).` };
|
|
@@ -164,28 +204,41 @@ export function validateToonString(toonString) {
|
|
|
164
204
|
return { isValid: false, error: `L${lineNumber}: Invalid un-indentation.` };
|
|
165
205
|
}
|
|
166
206
|
|
|
207
|
+
// After popping, count items in parent context if it's an array
|
|
167
208
|
const parentContext = contextStack[contextStack.length - 1];
|
|
168
209
|
if (parentContext.type === 'array') {
|
|
169
|
-
if (
|
|
210
|
+
if (parentContext.isTabular) {
|
|
211
|
+
parentContext.count++;
|
|
212
|
+
} else if (trimmedLine.match(REGEX.listItem)) {
|
|
170
213
|
parentContext.count++;
|
|
171
214
|
}
|
|
172
215
|
}
|
|
173
216
|
|
|
174
217
|
} else {
|
|
175
218
|
// Same Indent
|
|
219
|
+
// If array, count items
|
|
176
220
|
if (currentContext.type === 'array') {
|
|
177
|
-
if (
|
|
221
|
+
if (currentContext.isTabular) {
|
|
222
|
+
currentContext.count++;
|
|
223
|
+
} else if (trimmedLine.match(REGEX.listItem)) {
|
|
178
224
|
currentContext.count++;
|
|
179
225
|
}
|
|
180
226
|
}
|
|
181
227
|
}
|
|
182
228
|
|
|
229
|
+
// Update currentContext as it might have changed (push/pop)
|
|
230
|
+
currentContext = contextStack[contextStack.length - 1];
|
|
231
|
+
|
|
183
232
|
// --- Syntax Check ---
|
|
184
233
|
if (trimmedLine.match(REGEX.arrayKey) || trimmedLine.match(REGEX.rootArray)) {
|
|
185
234
|
if (startsTabular(trimmedLine)) isInsideTabularArray = true;
|
|
186
235
|
}
|
|
187
236
|
else if (trimmedLine.match(REGEX.mapKey)) { }
|
|
188
|
-
else if (trimmedLine.match(REGEX.listItem)) {
|
|
237
|
+
else if (trimmedLine.match(REGEX.listItem)) {
|
|
238
|
+
if (currentContext.type !== 'array') {
|
|
239
|
+
return { isValid: false, error: `L${lineNumber}: List item found in non-array context.` };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
189
242
|
else if (trimmedLine.includes(':')) {
|
|
190
243
|
if (!trimmedLine.match(REGEX.keyValue)) {
|
|
191
244
|
return { isValid: false, error: `L${lineNumber}: Invalid Key-Value assignment.` };
|
|
@@ -216,3 +269,12 @@ export function validateToonString(toonString) {
|
|
|
216
269
|
|
|
217
270
|
return { isValid: true, error: null };
|
|
218
271
|
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validates a TOON string for syntax and structural correctness (Async)
|
|
275
|
+
* @param {string} toonString - TOON string to validate
|
|
276
|
+
* @returns {{isValid: boolean, error: string|null}} Validation result
|
|
277
|
+
*/
|
|
278
|
+
export async function validateToonString(toonString) {
|
|
279
|
+
return validateToonStringSync(toonString);
|
|
280
|
+
}
|
package/src/xml.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Note: This module is designed for Node.js environments
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { encodeXmlReservedChars } from './utils.js';
|
|
6
|
+
import { jsonToToonSync, toonToJsonSync } from './json.js';
|
|
7
|
+
import { encodeXmlReservedChars, extractXmlFromString } from './utils.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Converts XML DOM to JSON object
|
|
@@ -115,60 +115,112 @@ function jsonObjectToXml(obj) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @param {string} xmlString
|
|
120
|
-
* @returns {string}
|
|
121
|
-
* @throws {Error} If XML is invalid or DOMParser is not available
|
|
118
|
+
* Internal core function to convert pure XML string to TOON (Sync)
|
|
119
|
+
* @param {string} xmlString
|
|
120
|
+
* @returns {string}
|
|
122
121
|
*/
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw new Error('Input must be a non-empty string');
|
|
126
|
-
}
|
|
122
|
+
function parseXmlToToonSync(xmlString) {
|
|
123
|
+
let Parser;
|
|
127
124
|
|
|
128
|
-
// Check if we're in a browser environment
|
|
129
125
|
if (typeof DOMParser !== 'undefined') {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
Parser = DOMParser;
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error('DOMParser is not available. For synchronous XML conversion in Node.js, please use the async version `xmlToToon` or polyfill global.DOMParser.');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parser = new Parser();
|
|
132
|
+
const xmlDoc = parser.parseFromString(
|
|
133
|
+
encodeXmlReservedChars(xmlString),
|
|
134
|
+
'application/xml'
|
|
135
|
+
);
|
|
135
136
|
|
|
137
|
+
// Check for parser errors (works in both browser and xmldom)
|
|
138
|
+
if (xmlDoc.querySelector) {
|
|
139
|
+
// Browser environment
|
|
136
140
|
const parserError = xmlDoc.querySelector('parsererror');
|
|
137
141
|
if (parserError) {
|
|
138
142
|
throw new Error(parserError.textContent);
|
|
139
143
|
}
|
|
144
|
+
} else {
|
|
145
|
+
// xmldom environment - check documentElement
|
|
146
|
+
if (xmlDoc.documentElement && xmlDoc.documentElement.nodeName === 'parsererror') {
|
|
147
|
+
throw new Error(xmlDoc.documentElement.textContent || 'XML parsing error');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const jsonObject = xmlToJsonObject(xmlDoc);
|
|
152
|
+
return jsonToToonSync(jsonObject);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Converts XML (or mixed text with XML) to TOON format (Synchronous)
|
|
157
|
+
* @param {string} xmlString - XML formatted string or mixed text
|
|
158
|
+
* @returns {string} TOON formatted string
|
|
159
|
+
* @throws {Error} If XML is invalid or DOMParser is not available
|
|
160
|
+
*/
|
|
161
|
+
export function xmlToToonSync(xmlString) {
|
|
162
|
+
if (!xmlString || typeof xmlString !== 'string') {
|
|
163
|
+
throw new Error('Input must be a non-empty string');
|
|
164
|
+
}
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
|
|
166
|
+
let convertedText = xmlString;
|
|
167
|
+
let iterationCount = 0;
|
|
168
|
+
const maxIterations = 100;
|
|
169
|
+
|
|
170
|
+
while (iterationCount < maxIterations) {
|
|
171
|
+
const xmlBlock = extractXmlFromString(convertedText);
|
|
172
|
+
if (!xmlBlock) break;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const toonString = parseXmlToToonSync(xmlBlock);
|
|
176
|
+
const toonOutput = toonString.trim();
|
|
177
|
+
convertedText = convertedText.replace(xmlBlock, toonOutput);
|
|
178
|
+
iterationCount++;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
143
182
|
}
|
|
144
183
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
184
|
+
return convertedText;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Converts XML (or mixed text with XML) to TOON format (Async)
|
|
189
|
+
* @param {string} xmlString - XML formatted string or mixed text
|
|
190
|
+
* @returns {Promise<string>} TOON formatted string
|
|
191
|
+
*/
|
|
192
|
+
export async function xmlToToon(xmlString) {
|
|
193
|
+
if (typeof DOMParser === 'undefined') {
|
|
194
|
+
try {
|
|
195
|
+
const { DOMParser: NodeDOMParser } = await import('xmldom');
|
|
196
|
+
global.DOMParser = NodeDOMParser;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
// Ignore if import fails, xmlToToonSync will throw appropriate error
|
|
199
|
+
}
|
|
158
200
|
}
|
|
201
|
+
return xmlToToonSync(xmlString);
|
|
159
202
|
}
|
|
160
203
|
|
|
161
204
|
/**
|
|
162
|
-
* Converts TOON to XML format
|
|
205
|
+
* Converts TOON to XML format (Synchronous)
|
|
163
206
|
* @param {string} toonString - TOON formatted string
|
|
164
207
|
* @returns {string} XML formatted string
|
|
165
208
|
* @throws {Error} If TOON is invalid
|
|
166
209
|
*/
|
|
167
|
-
export function
|
|
210
|
+
export function toonToXmlSync(toonString) {
|
|
168
211
|
if (!toonString || typeof toonString !== 'string') {
|
|
169
212
|
throw new Error('Input must be a non-empty string');
|
|
170
213
|
}
|
|
171
214
|
|
|
172
|
-
const jsonObject =
|
|
215
|
+
const jsonObject = toonToJsonSync(toonString);
|
|
173
216
|
return jsonObjectToXml(jsonObject);
|
|
174
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Converts TOON to XML format (Async)
|
|
221
|
+
* @param {string} toonString - TOON formatted string
|
|
222
|
+
* @returns {Promise<string>} XML formatted string
|
|
223
|
+
*/
|
|
224
|
+
export async function toonToXml(toonString) {
|
|
225
|
+
return toonToXmlSync(toonString);
|
|
226
|
+
}
|
package/src/yaml.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import yaml from 'js-yaml';
|
|
6
|
-
import {
|
|
6
|
+
import { jsonToToonSync, toonToJsonSync } from './json.js';
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Converts YAML to TOON format
|
|
10
|
-
* @param {string} yamlString - YAML formatted string
|
|
10
|
+
* Converts YAML (or mixed text with YAML) to TOON format (Synchronous)
|
|
11
|
+
* @param {string} yamlString - YAML formatted string or mixed text
|
|
11
12
|
* @returns {string} TOON formatted string
|
|
12
13
|
* @throws {Error} If YAML is invalid
|
|
13
14
|
*/
|
|
14
|
-
export function
|
|
15
|
+
export function yamlToToonSync(yamlString) {
|
|
15
16
|
if (!yamlString || typeof yamlString !== 'string') {
|
|
16
17
|
throw new Error('Input must be a non-empty string');
|
|
17
18
|
}
|
|
@@ -22,20 +23,38 @@ export function yamlToToon(yamlString) {
|
|
|
22
23
|
throw new Error("YAML parsing failed — cannot convert.");
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
return
|
|
26
|
+
return jsonToToonSync(jsonObject);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts YAML (or mixed text with YAML) to TOON format (Async)
|
|
31
|
+
* @param {string} yamlString - YAML formatted string or mixed text
|
|
32
|
+
* @returns {Promise<string>} TOON formatted string
|
|
33
|
+
*/
|
|
34
|
+
export async function yamlToToon(yamlString) {
|
|
35
|
+
return yamlToToonSync(yamlString);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
/**
|
|
29
|
-
* Converts TOON to YAML format
|
|
39
|
+
* Converts TOON to YAML format (Synchronous)
|
|
30
40
|
* @param {string} toonString - TOON formatted string
|
|
31
41
|
* @returns {string} YAML formatted string
|
|
32
42
|
* @throws {Error} If TOON is invalid
|
|
33
43
|
*/
|
|
34
|
-
export function
|
|
44
|
+
export function toonToYamlSync(toonString) {
|
|
35
45
|
if (!toonString || typeof toonString !== 'string') {
|
|
36
46
|
throw new Error('Input must be a non-empty string');
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
const jsonObject =
|
|
49
|
+
const jsonObject = toonToJsonSync(toonString);
|
|
40
50
|
return yaml.dump(jsonObject);
|
|
41
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Converts TOON to YAML format (Async)
|
|
55
|
+
* @param {string} toonString - TOON formatted string
|
|
56
|
+
* @returns {Promise<string>} YAML formatted string
|
|
57
|
+
*/
|
|
58
|
+
export async function toonToYaml(toonString) {
|
|
59
|
+
return toonToYamlSync(toonString);
|
|
60
|
+
}
|
package/test/basic.test.js
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import { test } from 'node:test';
|
|
7
7
|
import assert from 'node:assert';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { jsonToToonSync, toonToJsonSync } from '../src/json.js';
|
|
9
|
+
import { validateToonStringSync } from '../src/validator.js';
|
|
10
10
|
|
|
11
11
|
test('JSON to TOON - Simple Object', () => {
|
|
12
12
|
const input = { name: "Alice", age: 30, active: true };
|
|
13
|
-
const result =
|
|
13
|
+
const result = jsonToToonSync(input);
|
|
14
14
|
|
|
15
15
|
assert.ok(result.includes('name: "Alice"'));
|
|
16
16
|
assert.ok(result.includes('age: 30'));
|
|
@@ -19,7 +19,7 @@ test('JSON to TOON - Simple Object', () => {
|
|
|
19
19
|
|
|
20
20
|
test('JSON to TOON - Array of Primitives', () => {
|
|
21
21
|
const input = { numbers: [1, 2, 3, 4, 5] };
|
|
22
|
-
const result =
|
|
22
|
+
const result = jsonToToonSync(input);
|
|
23
23
|
|
|
24
24
|
assert.ok(result.includes('numbers[5]: 1, 2, 3, 4, 5'));
|
|
25
25
|
});
|
|
@@ -31,7 +31,7 @@ test('JSON to TOON - Tabular Array', () => {
|
|
|
31
31
|
{ id: 2, name: "Bob", active: false }
|
|
32
32
|
]
|
|
33
33
|
};
|
|
34
|
-
const result =
|
|
34
|
+
const result = jsonToToonSync(input);
|
|
35
35
|
|
|
36
36
|
assert.ok(result.includes('users[2]{id,name,active}:'));
|
|
37
37
|
assert.ok(result.includes('1,"Alice",true'));
|
|
@@ -40,7 +40,7 @@ test('JSON to TOON - Tabular Array', () => {
|
|
|
40
40
|
|
|
41
41
|
test('TOON to JSON - Simple Object', () => {
|
|
42
42
|
const input = `name: "Alice"\nage: 30\nactive: true`;
|
|
43
|
-
const result =
|
|
43
|
+
const result = toonToJsonSync(input);
|
|
44
44
|
|
|
45
45
|
assert.strictEqual(result.name, "Alice");
|
|
46
46
|
assert.strictEqual(result.age, 30);
|
|
@@ -49,7 +49,7 @@ test('TOON to JSON - Simple Object', () => {
|
|
|
49
49
|
|
|
50
50
|
test('TOON to JSON - Array of Primitives', () => {
|
|
51
51
|
const input = `numbers[5]: 1, 2, 3, 4, 5`;
|
|
52
|
-
const result =
|
|
52
|
+
const result = toonToJsonSync(input);
|
|
53
53
|
|
|
54
54
|
assert.ok(Array.isArray(result.numbers));
|
|
55
55
|
assert.strictEqual(result.numbers.length, 5);
|
|
@@ -58,7 +58,7 @@ test('TOON to JSON - Array of Primitives', () => {
|
|
|
58
58
|
|
|
59
59
|
test('TOON to JSON - Tabular Array', () => {
|
|
60
60
|
const input = `users[2]{id,name,active}:\n 1,"Alice",true\n 2,"Bob",false`;
|
|
61
|
-
const result =
|
|
61
|
+
const result = toonToJsonSync(input);
|
|
62
62
|
|
|
63
63
|
assert.ok(Array.isArray(result.users));
|
|
64
64
|
assert.strictEqual(result.users.length, 2);
|
|
@@ -79,15 +79,15 @@ test('Round-trip Conversion - Object', () => {
|
|
|
79
79
|
]
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
const toon =
|
|
83
|
-
const result =
|
|
82
|
+
const toon = jsonToToonSync(original);
|
|
83
|
+
const result = toonToJsonSync(toon);
|
|
84
84
|
|
|
85
85
|
assert.deepStrictEqual(result, original);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
test('Validator - Valid TOON', () => {
|
|
89
89
|
const input = `name: "Alice"\nage: 30`;
|
|
90
|
-
const result =
|
|
90
|
+
const result = validateToonStringSync(input);
|
|
91
91
|
|
|
92
92
|
assert.strictEqual(result.isValid, true);
|
|
93
93
|
assert.strictEqual(result.error, null);
|
|
@@ -95,7 +95,7 @@ test('Validator - Valid TOON', () => {
|
|
|
95
95
|
|
|
96
96
|
test('Validator - Invalid TOON (Array Size Mismatch)', () => {
|
|
97
97
|
const input = `items[3]: 1, 2`; // Declared 3, but only 2 items
|
|
98
|
-
const result =
|
|
98
|
+
const result = validateToonStringSync(input);
|
|
99
99
|
|
|
100
100
|
assert.strictEqual(result.isValid, false);
|
|
101
101
|
assert.ok(result.error.includes('Array size mismatch'));
|
|
@@ -103,7 +103,7 @@ test('Validator - Invalid TOON (Array Size Mismatch)', () => {
|
|
|
103
103
|
|
|
104
104
|
test('Validator - Valid Tabular Array', () => {
|
|
105
105
|
const input = `users[2]{id,name}:\n 1,"Alice"\n 2,"Bob"`;
|
|
106
|
-
const result =
|
|
106
|
+
const result = validateToonStringSync(input);
|
|
107
107
|
|
|
108
108
|
assert.strictEqual(result.isValid, true);
|
|
109
109
|
assert.strictEqual(result.error, null);
|
|
@@ -111,14 +111,14 @@ test('Validator - Valid Tabular Array', () => {
|
|
|
111
111
|
|
|
112
112
|
test('Edge Case - Empty Object', () => {
|
|
113
113
|
const input = {};
|
|
114
|
-
const result =
|
|
114
|
+
const result = jsonToToonSync(input);
|
|
115
115
|
|
|
116
116
|
assert.strictEqual(result.trim(), '');
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test('Edge Case - Null Value', () => {
|
|
120
120
|
const input = { value: null };
|
|
121
|
-
const result =
|
|
121
|
+
const result = jsonToToonSync(input);
|
|
122
122
|
|
|
123
123
|
assert.ok(result.includes('value: null'));
|
|
124
124
|
});
|
|
@@ -132,8 +132,8 @@ test('Edge Case - Nested Objects', () => {
|
|
|
132
132
|
}
|
|
133
133
|
};
|
|
134
134
|
|
|
135
|
-
const toon =
|
|
136
|
-
const result =
|
|
135
|
+
const toon = jsonToToonSync(input);
|
|
136
|
+
const result = toonToJsonSync(toon);
|
|
137
137
|
|
|
138
138
|
assert.deepStrictEqual(result, input);
|
|
139
139
|
});
|
package/test/converters.test.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import { test } from 'node:test';
|
|
7
7
|
import assert from 'node:assert';
|
|
8
|
-
import {
|
|
9
|
-
import { xmlToToon,
|
|
10
|
-
import { csvToToon, csvToToonSync,
|
|
8
|
+
import { yamlToToonSync, toonToYamlSync } from '../src/yaml.js';
|
|
9
|
+
import { xmlToToon, toonToXmlSync } from '../src/xml.js';
|
|
10
|
+
import { csvToToon, csvToToonSync, toonToCsvSync } from '../src/csv.js';
|
|
11
11
|
|
|
12
12
|
// --- YAML Tests ---
|
|
13
13
|
|
|
@@ -16,14 +16,14 @@ test('YAML to TOON - Simple Object', () => {
|
|
|
16
16
|
name: Alice
|
|
17
17
|
age: 30
|
|
18
18
|
`;
|
|
19
|
-
const toon =
|
|
19
|
+
const toon = yamlToToonSync(yaml);
|
|
20
20
|
assert.ok(toon.includes('name: "Alice"'));
|
|
21
21
|
assert.ok(toon.includes('age: 30'));
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('TOON to YAML - Simple Object', () => {
|
|
25
25
|
const toon = `name: "Alice"\nage: 30`;
|
|
26
|
-
const yaml =
|
|
26
|
+
const yaml = toonToYamlSync(toon);
|
|
27
27
|
assert.ok(yaml.includes('name: Alice'));
|
|
28
28
|
assert.ok(yaml.includes('age: 30'));
|
|
29
29
|
});
|
|
@@ -36,7 +36,7 @@ user:
|
|
|
36
36
|
- admin
|
|
37
37
|
- editor
|
|
38
38
|
`;
|
|
39
|
-
const toon =
|
|
39
|
+
const toon = yamlToToonSync(yaml);
|
|
40
40
|
assert.ok(toon.includes('user:'));
|
|
41
41
|
assert.ok(toon.includes('name: "Alice"'));
|
|
42
42
|
assert.ok(toon.includes('roles[2]:'));
|
|
@@ -56,7 +56,7 @@ test('XML to TOON - Simple Element', async () => {
|
|
|
56
56
|
|
|
57
57
|
test('TOON to XML - Simple Element', () => {
|
|
58
58
|
const toon = `user:\n name: "Alice"\n age: 30`;
|
|
59
|
-
const xml =
|
|
59
|
+
const xml = toonToXmlSync(toon);
|
|
60
60
|
|
|
61
61
|
assert.ok(xml.includes('<user>'));
|
|
62
62
|
assert.ok(xml.includes('<name>Alice</name>'));
|
|
@@ -111,7 +111,7 @@ test('TOON to CSV - Basic', () => {
|
|
|
111
111
|
"Alice","Admin"
|
|
112
112
|
"Bob","User"
|
|
113
113
|
`;
|
|
114
|
-
const csv =
|
|
114
|
+
const csv = toonToCsvSync(toon);
|
|
115
115
|
|
|
116
116
|
assert.ok(csv.includes('name,role'));
|
|
117
117
|
assert.ok(csv.includes('Alice,Admin'));
|
|
@@ -124,7 +124,7 @@ Alice,100
|
|
|
124
124
|
Bob,95`;
|
|
125
125
|
|
|
126
126
|
const toon = await csvToToon(originalCsv);
|
|
127
|
-
const finalCsv =
|
|
127
|
+
const finalCsv = toonToCsvSync(toon);
|
|
128
128
|
|
|
129
129
|
// Note: PapaParse might add/remove quotes or change spacing, so exact match isn't always guaranteed
|
|
130
130
|
// But content should be same
|