toon-parser 2.0.0 → 2.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/README.md +36 -6
- package/dist/cjs.test.cjs +46 -0
- package/dist/complex.test.cjs +35 -0
- package/dist/coverage.test.cjs +49 -0
- package/dist/csv.cjs +150 -0
- package/dist/csv.d.ts +20 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/csv.edge.test.cjs +20 -0
- package/dist/csv.js +142 -0
- package/dist/csv.js.map +1 -0
- package/dist/csv.test.cjs +27 -0
- package/dist/edgecases.test.cjs +23 -0
- package/dist/extra.test.cjs +19 -0
- package/dist/html.cjs +176 -0
- package/dist/html.d.ts +17 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.edge.test.cjs +16 -0
- package/dist/html.js +181 -0
- package/dist/html.js.map +1 -0
- package/dist/html.test.cjs +28 -0
- package/dist/index.cjs +9 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.cjs +59 -0
- package/dist/limits.test.cjs +21 -0
- package/dist/log.cjs +57 -0
- package/dist/log.clf.redos.test.cjs +15 -0
- package/dist/log.d.ts +9 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +36 -0
- package/dist/log.js.map +1 -0
- package/dist/log.test.cjs +20 -0
- package/dist/property.test.cjs +60 -0
- package/dist/smoke.test.cjs +132 -0
- package/dist/snapshots.test.cjs +21 -0
- package/dist/tabular.test.cjs +21 -0
- package/dist/url.cjs +72 -0
- package/dist/url.d.ts +3 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +57 -0
- package/dist/url.js.map +1 -0
- package/dist/url.test.cjs +25 -0
- package/dist/xml.cjs +22 -0
- package/dist/xml.d.ts +1 -0
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.edge.test.cjs +16 -0
- package/dist/xml.js +22 -1
- package/dist/xml.js.map +1 -1
- package/dist/xml.test.cjs +86 -0
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# toon-parser
|
|
2
2
|
|
|
3
3
|
[](https://github.com/BranLang/toon-parser/actions/workflows/ci.yml)
|
|
4
|
+
[](#coverage)
|
|
4
5
|
[](https://www.npmjs.com/package/toon-parser)
|
|
6
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
5
7
|
[](https://opensource.org/licenses/MIT)
|
|
6
8
|
|
|
7
9
|
Safe JSON ⇆ TOON encoder/decoder with strict validation and prototype-pollution guards.
|
|
@@ -14,6 +16,9 @@ npm install toon-parser
|
|
|
14
16
|
|
|
15
17
|
Note: this package supports both ESM and CommonJS consumers (CJS builds are available as `dist/index.cjs`). The package requires Node >= 18 per `engines` in `package.json`.
|
|
16
18
|
|
|
19
|
+
## New in 2.1.0
|
|
20
|
+
- **HTML/CSV/Log/URL Support**: Dedicated parsers for common formats to leverage Toon's structure.
|
|
21
|
+
|
|
17
22
|
## New in 2.0.0
|
|
18
23
|
- **XML Support**: Convert XML strings directly to TOON with `xmlToToon`.
|
|
19
24
|
- **Date Support**: Automatically converts `Date` objects to ISO strings.
|
|
@@ -65,12 +70,37 @@ const toon = xmlToToon('<user id="1">Alice</user>');
|
|
|
65
70
|
// "@_id": 1
|
|
66
71
|
```
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
### `htmlToToon(html, options?) => string`
|
|
74
|
+
|
|
75
|
+
Parses HTML string to Toon. Uses `node-html-parser`.
|
|
76
|
+
|
|
77
|
+
### `csvToToon(csv, options?) => string`
|
|
78
|
+
|
|
79
|
+
Parses CSV string. Options:
|
|
80
|
+
- `delimiter` (default `,`)
|
|
81
|
+
- `hasHeader` (default `true`)
|
|
82
|
+
|
|
83
|
+
### `urlToToon(urlOrQs, options?) => string`
|
|
84
|
+
Parses URL query strings to Toon object. Expands dotted/bracket notation (e.g. `user[name]`).
|
|
85
|
+
|
|
86
|
+
### `logToToon(log, options?) => string`
|
|
87
|
+
Parses logs. Options:
|
|
88
|
+
- `format`: `'auto'` | `'clf'` | `'json'`
|
|
89
|
+
|
|
90
|
+
### `csvToJson(csv, options?) => unknown[]`
|
|
91
|
+
Lightweight CSV to JSON helper. Throws when row widths mismatch headers or when the delimiter is not a single character.
|
|
92
|
+
|
|
93
|
+
### `htmlToJson(html) => { children: ... }`
|
|
94
|
+
Parses HTML into a simplified JSON tree. Performs a minimal tag-balance check and trims whitespace-only nodes. Not intended for arbitrary HTML with scripts/styles.
|
|
95
|
+
|
|
96
|
+
### `xmlToJson(xml, options?) => unknown`
|
|
97
|
+
Validates XML before parsing; returns `{}` for empty input and throws on malformed XML.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
> [!WARNING]
|
|
101
|
+
> **Security Note:** While `fast-xml-parser` v5 is generally secure by default, overriding `xmlOptions` can alter security properties (e.g., enabling entity expansion). Only enable such features if you trust the source XML.
|
|
102
|
+
|
|
103
|
+
Options:
|
|
74
104
|
- `indent` (number, default `2`): spaces per indentation level.
|
|
75
105
|
- `delimiter` (`,` | `|` | `\t`, default `,`): delimiter for inline arrays and tabular rows.
|
|
76
106
|
- `sortKeys` (boolean, default `false`): sort object keys alphabetically instead of preserving encounter order.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var import_vitest = require("vitest");
|
|
25
|
+
var esmModule = __toESM(require("./index"), 1);
|
|
26
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
27
|
+
var import_path = __toESM(require("path"), 1);
|
|
28
|
+
const distPath = import_path.default.join(__dirname, "..", "dist", "index.cjs");
|
|
29
|
+
(0, import_vitest.describe)("CJS built package", () => {
|
|
30
|
+
(0, import_vitest.it)("exposes same API as ESM source (if built)", () => {
|
|
31
|
+
if (!import_fs.default.existsSync(distPath)) {
|
|
32
|
+
(0, import_vitest.expect)(true).toBeTruthy();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const cjs = require(distPath);
|
|
36
|
+
(0, import_vitest.expect)(typeof cjs.jsonToToon).toBe("function");
|
|
37
|
+
(0, import_vitest.expect)(typeof cjs.toonToJson).toBe("function");
|
|
38
|
+
const sample = { a: 1, b: "x" };
|
|
39
|
+
const e = esmModule.jsonToToon(sample);
|
|
40
|
+
const d = cjs.jsonToToon(sample);
|
|
41
|
+
(0, import_vitest.expect)(e).toBeDefined();
|
|
42
|
+
(0, import_vitest.expect)(d).toBeDefined();
|
|
43
|
+
(0, import_vitest.expect)(esmModule.toonToJson(e)).toEqual(sample);
|
|
44
|
+
(0, import_vitest.expect)(cjs.toonToJson(d)).toEqual(sample);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_index = require("./index");
|
|
4
|
+
(0, import_vitest.describe)("Complex Scenarios", () => {
|
|
5
|
+
(0, import_vitest.describe)("CSV Complex Escaping", () => {
|
|
6
|
+
(0, import_vitest.it)("handles newlines and escaped quotes inside quoted fields", () => {
|
|
7
|
+
const csv = `"note","data"
|
|
8
|
+
"line1
|
|
9
|
+
line2","has ""quotes"" inside"`;
|
|
10
|
+
const toon = (0, import_index.csvToToon)(csv);
|
|
11
|
+
(0, import_vitest.expect)(toon).toContain('"line1\\nline2"');
|
|
12
|
+
(0, import_vitest.expect)(toon).toContain('"has \\"quotes\\" inside"');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
(0, import_vitest.describe)("HTML Deep Nesting & Attributes", () => {
|
|
16
|
+
(0, import_vitest.it)("preserves attributes in deeply nested lists", () => {
|
|
17
|
+
const html = `<ul>
|
|
18
|
+
<li class="item" data-id="1">Item 1</li>
|
|
19
|
+
<li class="item" data-id="2">Item 2</li>
|
|
20
|
+
</ul>`;
|
|
21
|
+
const toon = (0, import_index.htmlToToon)(html);
|
|
22
|
+
(0, import_vitest.expect)(toon).toContain("ul:");
|
|
23
|
+
(0, import_vitest.expect)(toon).toContain("li:");
|
|
24
|
+
(0, import_vitest.expect)(toon).toContain('"@_class": item');
|
|
25
|
+
(0, import_vitest.expect)(toon).toContain('"@_data-id": "1"');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
(0, import_vitest.describe)("URL Array Syntax", () => {
|
|
29
|
+
(0, import_vitest.it)("expands bracket notation deeply", () => {
|
|
30
|
+
const url = "sort[order]=asc&sort[fields][]=name&sort[fields][]=date";
|
|
31
|
+
const toon = (0, import_index.urlToToon)(url);
|
|
32
|
+
(0, import_vitest.expect)(toon).toContain("order: asc");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_index = require("./index");
|
|
4
|
+
(0, import_vitest.describe)("Coverage & Edge Case Gaps", () => {
|
|
5
|
+
(0, import_vitest.describe)("decodeKey Invalid Inputs", () => {
|
|
6
|
+
(0, import_vitest.it)("throws on unquoted key starting with invalid character", () => {
|
|
7
|
+
(0, import_vitest.expect)(() => (0, import_index.toonToJson)('123key: "val"')).toThrow(/Invalid key token/);
|
|
8
|
+
});
|
|
9
|
+
(0, import_vitest.it)("throws on unquoted key with special characters", () => {
|
|
10
|
+
(0, import_vitest.expect)(() => (0, import_index.toonToJson)('key+val: "val"')).toThrow(/Invalid key token/);
|
|
11
|
+
});
|
|
12
|
+
(0, import_vitest.it)("throws on unquoted key containing delimiter (fails regex anyway)", () => {
|
|
13
|
+
(0, import_vitest.expect)(() => (0, import_index.toonToJson)('key,val: "val"')).toThrow(/Invalid key token/);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
(0, import_vitest.describe)("Tabular Fallback", () => {
|
|
17
|
+
(0, import_vitest.it)("falls back to list format if object values are non-primitive", () => {
|
|
18
|
+
const data = [
|
|
19
|
+
{ id: 1, info: { meta: "not-primitive" } },
|
|
20
|
+
{ id: 2, info: { meta: "x" } }
|
|
21
|
+
];
|
|
22
|
+
const encoded = (0, import_index.jsonToToon)(data);
|
|
23
|
+
(0, import_vitest.expect)(encoded).not.toContain("]{id,info}");
|
|
24
|
+
(0, import_vitest.expect)(encoded).toContain("-");
|
|
25
|
+
(0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
|
|
26
|
+
});
|
|
27
|
+
(0, import_vitest.it)("falls back to list format if objects have different keys", () => {
|
|
28
|
+
const data = [
|
|
29
|
+
{ id: 1, a: 1 },
|
|
30
|
+
{ id: 2, b: 2 }
|
|
31
|
+
];
|
|
32
|
+
const encoded = (0, import_index.jsonToToon)(data);
|
|
33
|
+
(0, import_vitest.expect)(encoded).not.toContain("]{id,a}");
|
|
34
|
+
(0, import_vitest.expect)((0, import_index.toonToJson)(encoded)).toEqual(data);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
(0, import_vitest.describe)("Circular References", () => {
|
|
38
|
+
(0, import_vitest.it)("throws error on circular reference", () => {
|
|
39
|
+
const a = { name: "a" };
|
|
40
|
+
a.self = a;
|
|
41
|
+
(0, import_vitest.expect)(() => (0, import_index.jsonToToon)(a)).toThrow(/Maximum depth \d+ exceeded/);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
(0, import_vitest.describe)("Tabular Header Pollution", () => {
|
|
45
|
+
(0, import_vitest.it)("throws if tabular header contains disallowed keys when row is parsed", () => {
|
|
46
|
+
(0, import_vitest.expect)(() => (0, import_index.toonToJson)("[1]{__proto__}:\n 1")).toThrow(/Disallowed key "__proto__"/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
package/dist/csv.cjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var csv_exports = {};
|
|
20
|
+
__export(csv_exports, {
|
|
21
|
+
csvToJson: () => csvToJson,
|
|
22
|
+
csvToToon: () => csvToToon
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(csv_exports);
|
|
25
|
+
var import_index = require("./index.cjs");
|
|
26
|
+
function csvToToon(csv, options = {}) {
|
|
27
|
+
const delimiter = options.delimiter ?? ",";
|
|
28
|
+
if (delimiter.length !== 1) {
|
|
29
|
+
throw new Error("Delimiter must be a single character");
|
|
30
|
+
}
|
|
31
|
+
const hasHeader = options.hasHeader ?? true;
|
|
32
|
+
const rows = parseCsv(csv, delimiter);
|
|
33
|
+
if (rows.length === 0) {
|
|
34
|
+
return (0, import_index.jsonToToon)([], options);
|
|
35
|
+
}
|
|
36
|
+
if (hasHeader) {
|
|
37
|
+
const header = rows[0];
|
|
38
|
+
if (!header) return (0, import_index.jsonToToon)([], options);
|
|
39
|
+
const width = header.length;
|
|
40
|
+
const dataRows = rows.slice(1);
|
|
41
|
+
for (const row of dataRows) {
|
|
42
|
+
if (row.length !== width) {
|
|
43
|
+
throw new Error("Malformed CSV: row length mismatch");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const data = dataRows.map((row) => {
|
|
47
|
+
const obj = {};
|
|
48
|
+
header.forEach((key, index) => {
|
|
49
|
+
obj[key] = inferType(row[index] ?? "");
|
|
50
|
+
});
|
|
51
|
+
return obj;
|
|
52
|
+
});
|
|
53
|
+
return (0, import_index.jsonToToon)(data, options);
|
|
54
|
+
} else {
|
|
55
|
+
const data = rows.map((row) => row.map((cell) => inferType(cell)));
|
|
56
|
+
return (0, import_index.jsonToToon)(data, options);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function csvToJson(csv, options = {}) {
|
|
60
|
+
const delimiter = options.delimiter ?? ",";
|
|
61
|
+
if (delimiter.length !== 1) {
|
|
62
|
+
throw new Error("Delimiter must be a single character");
|
|
63
|
+
}
|
|
64
|
+
const hasHeader = options.hasHeader ?? true;
|
|
65
|
+
const rows = parseCsv(csv, delimiter);
|
|
66
|
+
if (rows.length === 0) return [];
|
|
67
|
+
if (hasHeader) {
|
|
68
|
+
const header = rows[0];
|
|
69
|
+
if (!header || header.length === 0) return [];
|
|
70
|
+
const width = header.length;
|
|
71
|
+
const body = rows.slice(1);
|
|
72
|
+
for (const row of body) {
|
|
73
|
+
if (row.length !== width) {
|
|
74
|
+
throw new Error("Malformed CSV: row length mismatch");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return body.map((row) => {
|
|
78
|
+
const obj = {};
|
|
79
|
+
header.forEach((key, idx) => {
|
|
80
|
+
obj[key] = inferType(row[idx] ?? "");
|
|
81
|
+
});
|
|
82
|
+
return obj;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return rows.map((row) => row.map((cell) => inferType(cell)));
|
|
86
|
+
}
|
|
87
|
+
function parseCsv(text, delimiter) {
|
|
88
|
+
const rows = [];
|
|
89
|
+
let currentRow = [];
|
|
90
|
+
let currentCell = "";
|
|
91
|
+
let inQuote = false;
|
|
92
|
+
for (let i = 0; i < text.length; i++) {
|
|
93
|
+
const char = text[i];
|
|
94
|
+
const nextChar = text[i + 1];
|
|
95
|
+
if (inQuote) {
|
|
96
|
+
if (char === '"') {
|
|
97
|
+
if (nextChar === '"') {
|
|
98
|
+
currentCell += '"';
|
|
99
|
+
i++;
|
|
100
|
+
} else {
|
|
101
|
+
inQuote = false;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
currentCell += char;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
if (char === '"') {
|
|
108
|
+
inQuote = true;
|
|
109
|
+
} else if (char === delimiter) {
|
|
110
|
+
currentRow.push(currentCell);
|
|
111
|
+
currentCell = "";
|
|
112
|
+
} else if (char === "\r" || char === "\n") {
|
|
113
|
+
if (char === "\r" && nextChar === "\n") i++;
|
|
114
|
+
currentRow.push(currentCell);
|
|
115
|
+
rows.push(currentRow);
|
|
116
|
+
currentRow = [];
|
|
117
|
+
currentCell = "";
|
|
118
|
+
} else {
|
|
119
|
+
currentCell += char;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (currentCell || currentRow.length > 0) {
|
|
124
|
+
currentRow.push(currentCell);
|
|
125
|
+
rows.push(currentRow);
|
|
126
|
+
}
|
|
127
|
+
if (rows.length > 0) {
|
|
128
|
+
const last = rows[rows.length - 1];
|
|
129
|
+
if (last && last.length === 1 && last[0] === "") {
|
|
130
|
+
rows.pop();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return rows;
|
|
134
|
+
}
|
|
135
|
+
function inferType(val) {
|
|
136
|
+
if (val === "true") return true;
|
|
137
|
+
if (val === "false") return false;
|
|
138
|
+
if (val === "null") return "null";
|
|
139
|
+
if (val.trim() === "") return "";
|
|
140
|
+
const num = Number(val);
|
|
141
|
+
if (!isNaN(num) && !val.includes(",")) {
|
|
142
|
+
return num;
|
|
143
|
+
}
|
|
144
|
+
return val;
|
|
145
|
+
}
|
|
146
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
147
|
+
0 && (module.exports = {
|
|
148
|
+
csvToJson,
|
|
149
|
+
csvToToon
|
|
150
|
+
});
|
package/dist/csv.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { JsonToToonOptions } from './index.js';
|
|
2
|
+
export interface CsvToToonOptions extends Omit<JsonToToonOptions, 'delimiter'> {
|
|
3
|
+
/**
|
|
4
|
+
* Character used to separate fields. Defaults to comma.
|
|
5
|
+
*/
|
|
6
|
+
delimiter?: string;
|
|
7
|
+
/**
|
|
8
|
+
* If true, the first row is treated as the header row.
|
|
9
|
+
* If false, the output will be a list of arrays instead of a tabular array.
|
|
10
|
+
* Defaults to true.
|
|
11
|
+
*/
|
|
12
|
+
hasHeader?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function csvToToon(csv: string, options?: CsvToToonOptions): string;
|
|
15
|
+
export interface CsvToJsonOptions {
|
|
16
|
+
delimiter?: string;
|
|
17
|
+
hasHeader?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function csvToJson(csv: string, options?: CsvToJsonOptions): unknown[];
|
|
20
|
+
//# sourceMappingURL=csv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC;IAC5E;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAqC7E;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,EAAE,CAiChF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_csv = require("../src/csv");
|
|
3
|
+
var import_vitest = require("vitest");
|
|
4
|
+
(0, import_vitest.test)("malformed CSV throws error", () => {
|
|
5
|
+
const malformed = "a,b\n1,2,3";
|
|
6
|
+
(0, import_vitest.expect)(() => (0, import_csv.csvToJson)(malformed)).toThrow();
|
|
7
|
+
});
|
|
8
|
+
(0, import_vitest.test)("empty CSV returns empty array", () => {
|
|
9
|
+
const empty = "";
|
|
10
|
+
const result = (0, import_csv.csvToJson)(empty);
|
|
11
|
+
(0, import_vitest.expect)(result).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
(0, import_vitest.test)("multi-character delimiter is rejected", () => {
|
|
14
|
+
const data = "a|b\n1|2";
|
|
15
|
+
(0, import_vitest.expect)(() => (0, import_csv.csvToJson)(data, { delimiter: "||" })).toThrow();
|
|
16
|
+
});
|
|
17
|
+
(0, import_vitest.test)("row length mismatch throws in csvToToon", () => {
|
|
18
|
+
const malformed = "a,b\n1,2,3";
|
|
19
|
+
(0, import_vitest.expect)(() => (0, import_csv.csvToToon)(malformed)).toThrow();
|
|
20
|
+
});
|
package/dist/csv.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { jsonToToon } from './index.js';
|
|
2
|
+
export function csvToToon(csv, options = {}) {
|
|
3
|
+
const delimiter = options.delimiter ?? ',';
|
|
4
|
+
if (delimiter.length !== 1) {
|
|
5
|
+
throw new Error('Delimiter must be a single character');
|
|
6
|
+
}
|
|
7
|
+
const hasHeader = options.hasHeader ?? true;
|
|
8
|
+
const rows = parseCsv(csv, delimiter);
|
|
9
|
+
if (rows.length === 0) {
|
|
10
|
+
return jsonToToon([], options);
|
|
11
|
+
}
|
|
12
|
+
if (hasHeader) {
|
|
13
|
+
const header = rows[0];
|
|
14
|
+
if (!header)
|
|
15
|
+
return jsonToToon([], options);
|
|
16
|
+
const width = header.length;
|
|
17
|
+
const dataRows = rows.slice(1);
|
|
18
|
+
for (const row of dataRows) {
|
|
19
|
+
if (row.length !== width) {
|
|
20
|
+
throw new Error('Malformed CSV: row length mismatch');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const data = dataRows.map(row => {
|
|
24
|
+
const obj = {};
|
|
25
|
+
header.forEach((key, index) => {
|
|
26
|
+
obj[key] = inferType(row[index] ?? '');
|
|
27
|
+
});
|
|
28
|
+
return obj;
|
|
29
|
+
});
|
|
30
|
+
return jsonToToon(data, options);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// No header: array of arrays
|
|
34
|
+
const data = rows.map(row => row.map(cell => inferType(cell)));
|
|
35
|
+
return jsonToToon(data, options);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function csvToJson(csv, options = {}) {
|
|
39
|
+
const delimiter = options.delimiter ?? ',';
|
|
40
|
+
if (delimiter.length !== 1) {
|
|
41
|
+
throw new Error('Delimiter must be a single character');
|
|
42
|
+
}
|
|
43
|
+
const hasHeader = options.hasHeader ?? true;
|
|
44
|
+
const rows = parseCsv(csv, delimiter);
|
|
45
|
+
if (rows.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
if (hasHeader) {
|
|
48
|
+
const header = rows[0];
|
|
49
|
+
if (!header || header.length === 0)
|
|
50
|
+
return [];
|
|
51
|
+
const width = header.length;
|
|
52
|
+
const body = rows.slice(1);
|
|
53
|
+
for (const row of body) {
|
|
54
|
+
if (row.length !== width) {
|
|
55
|
+
throw new Error('Malformed CSV: row length mismatch');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return body.map(row => {
|
|
59
|
+
const obj = {};
|
|
60
|
+
header.forEach((key, idx) => {
|
|
61
|
+
obj[key] = inferType(row[idx] ?? '');
|
|
62
|
+
});
|
|
63
|
+
return obj;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// no header: return array of arrays with primitive inference
|
|
67
|
+
return rows.map(row => row.map(cell => inferType(cell)));
|
|
68
|
+
}
|
|
69
|
+
function parseCsv(text, delimiter) {
|
|
70
|
+
const rows = [];
|
|
71
|
+
let currentRow = [];
|
|
72
|
+
let currentCell = '';
|
|
73
|
+
let inQuote = false;
|
|
74
|
+
for (let i = 0; i < text.length; i++) {
|
|
75
|
+
const char = text[i];
|
|
76
|
+
const nextChar = text[i + 1];
|
|
77
|
+
if (inQuote) {
|
|
78
|
+
if (char === '"') {
|
|
79
|
+
if (nextChar === '"') {
|
|
80
|
+
currentCell += '"';
|
|
81
|
+
i++; // skip escaped quote
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
inQuote = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
currentCell += char;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
if (char === '"') {
|
|
93
|
+
inQuote = true;
|
|
94
|
+
}
|
|
95
|
+
else if (char === delimiter) {
|
|
96
|
+
currentRow.push(currentCell);
|
|
97
|
+
currentCell = '';
|
|
98
|
+
}
|
|
99
|
+
else if (char === '\r' || char === '\n') {
|
|
100
|
+
if (char === '\r' && nextChar === '\n')
|
|
101
|
+
i++;
|
|
102
|
+
currentRow.push(currentCell);
|
|
103
|
+
rows.push(currentRow);
|
|
104
|
+
currentRow = [];
|
|
105
|
+
currentCell = '';
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
currentCell += char;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Flush last cell/row
|
|
113
|
+
if (currentCell || currentRow.length > 0) {
|
|
114
|
+
currentRow.push(currentCell);
|
|
115
|
+
rows.push(currentRow);
|
|
116
|
+
}
|
|
117
|
+
// Clean up empty trailing row if common in file save
|
|
118
|
+
if (rows.length > 0) {
|
|
119
|
+
const last = rows[rows.length - 1];
|
|
120
|
+
if (last && last.length === 1 && last[0] === '') {
|
|
121
|
+
rows.pop();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return rows;
|
|
125
|
+
}
|
|
126
|
+
function inferType(val) {
|
|
127
|
+
if (val === 'true')
|
|
128
|
+
return true;
|
|
129
|
+
if (val === 'false')
|
|
130
|
+
return false;
|
|
131
|
+
if (val === 'null')
|
|
132
|
+
return 'null'; // keep string null? or null value? usually string in CSV
|
|
133
|
+
// Attempt number conversion
|
|
134
|
+
if (val.trim() === '')
|
|
135
|
+
return '';
|
|
136
|
+
const num = Number(val);
|
|
137
|
+
if (!isNaN(num) && !val.includes(',')) { // simple check
|
|
138
|
+
return num;
|
|
139
|
+
}
|
|
140
|
+
return val;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=csv.js.map
|
package/dist/csv.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","sourceRoot":"","sources":["../src/csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAe3D,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,UAA4B,EAAE;IACnE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC;IAC3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAE5C,MAAM,IAAI,GAAe,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAElD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,UAAU,CAAC,EAAE,EAAE,OAA4B,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM;YAAE,OAAO,UAAU,CAAC,EAAE,EAAE,OAA4B,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAC9B,MAAM,GAAG,GAA4B,EAAE,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;gBAC5B,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;QACH,OAAO,UAAU,CAAC,IAAI,EAAE,OAA4B,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,6BAA6B;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/D,OAAO,UAAU,CAAC,IAAI,EAAE,OAA4B,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAOD,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,UAA4B,EAAE;IACnE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC;IAC3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAEtC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAE5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YACpB,MAAM,GAAG,GAA4B,EAAE,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBAC1B,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC;QACb,CAAC,CAAC,CAAC;IACL,CAAC;IAED,6DAA6D;IAC7D,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,SAAiB;IAC/C,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,IAAI,UAAU,GAAa,EAAE,CAAC;IAC9B,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE7B,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;oBACrB,WAAW,IAAI,GAAG,CAAC;oBACnB,CAAC,EAAE,CAAC,CAAC,qBAAqB;gBAC5B,CAAC;qBAAM,CAAC;oBACN,OAAO,GAAG,KAAK,CAAC;gBAClB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,WAAW,IAAI,IAAI,CAAC;YACtB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC7B,WAAW,GAAG,EAAE,CAAC;YACnB,CAAC;iBAAM,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC1C,IAAI,IAAI,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI;oBAAE,CAAC,EAAE,CAAC;gBAC5C,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBACtB,UAAU,GAAG,EAAE,CAAC;gBAChB,WAAW,GAAG,EAAE,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,WAAW,IAAI,IAAI,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,sBAAsB;IACtB,IAAI,WAAW,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;IAED,qDAAqD;IACrD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,GAAG,EAAE,CAAC;QACf,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,GAAG,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC,CAAC,yDAAyD;IAC5F,4BAA4B;IAC5B,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,eAAe;QACrD,OAAO,GAAG,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_csv = require("./csv");
|
|
4
|
+
(0, import_vitest.describe)("csvToToon", () => {
|
|
5
|
+
(0, import_vitest.it)("converts simple CSV with header to table", () => {
|
|
6
|
+
const csv = `id,name,role
|
|
7
|
+
1,Alice,Admin
|
|
8
|
+
2,Bob,User`;
|
|
9
|
+
const toon = (0, import_csv.csvToToon)(csv);
|
|
10
|
+
(0, import_vitest.expect)(toon).toContain("[2]{id,name,role}:");
|
|
11
|
+
(0, import_vitest.expect)(toon).toContain("1,Alice,Admin");
|
|
12
|
+
(0, import_vitest.expect)(toon).toContain("2,Bob,User");
|
|
13
|
+
});
|
|
14
|
+
(0, import_vitest.it)("handles quoted values", () => {
|
|
15
|
+
const csv = `id,desc
|
|
16
|
+
1,"Hello, World"`;
|
|
17
|
+
const toon = (0, import_csv.csvToToon)(csv);
|
|
18
|
+
(0, import_vitest.expect)(toon).toContain('"Hello, World"');
|
|
19
|
+
});
|
|
20
|
+
(0, import_vitest.it)("handles headerless csv", () => {
|
|
21
|
+
const csv = `1,2
|
|
22
|
+
3,4`;
|
|
23
|
+
const toon = (0, import_csv.csvToToon)(csv, { hasHeader: false });
|
|
24
|
+
(0, import_vitest.expect)(toon).toContain("- [2]: 1,2");
|
|
25
|
+
(0, import_vitest.expect)(toon).toContain("- [2]: 3,4");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_index = require("./index");
|
|
4
|
+
(0, import_vitest.describe)("Edge case tests", () => {
|
|
5
|
+
(0, import_vitest.it)("round-trips arrays with empty objects", () => {
|
|
6
|
+
const data = { arr: [{}, { id: 1 }] };
|
|
7
|
+
const t = (0, import_index.jsonToToon)(data);
|
|
8
|
+
const back = (0, import_index.toonToJson)(t);
|
|
9
|
+
(0, import_vitest.expect)(back).toEqual(data);
|
|
10
|
+
});
|
|
11
|
+
(0, import_vitest.it)("round-trips keys with punctuation (quoted)", () => {
|
|
12
|
+
const data = { "a b": 1, "!ok": 2 };
|
|
13
|
+
const t = (0, import_index.jsonToToon)(data);
|
|
14
|
+
const back = (0, import_index.toonToJson)(t);
|
|
15
|
+
(0, import_vitest.expect)(back).toEqual(data);
|
|
16
|
+
});
|
|
17
|
+
(0, import_vitest.it)("handles strings with delimiter and colon characters by quoting", () => {
|
|
18
|
+
const data = { val: "1,2:3|x" };
|
|
19
|
+
const t = (0, import_index.jsonToToon)(data, { delimiter: "," });
|
|
20
|
+
const back = (0, import_index.toonToJson)(t);
|
|
21
|
+
(0, import_vitest.expect)(back).toEqual(data);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_vitest = require("vitest");
|
|
3
|
+
var import_index = require("./index");
|
|
4
|
+
(0, import_vitest.describe)("Toon parser strict vs non-strict and diagnostics", () => {
|
|
5
|
+
(0, import_vitest.it)("allows inline length mismatch when strict false", () => {
|
|
6
|
+
const result = (0, import_index.toonToJson)("vals[2]: 1", { strict: false });
|
|
7
|
+
(0, import_vitest.expect)(result).toEqual({ vals: [1] });
|
|
8
|
+
});
|
|
9
|
+
(0, import_vitest.it)("includes line numbers in errors", () => {
|
|
10
|
+
try {
|
|
11
|
+
(0, import_index.toonToJson)("vals[2]: 1", { strict: true });
|
|
12
|
+
throw new Error("Should have thrown");
|
|
13
|
+
} catch (err) {
|
|
14
|
+
(0, import_vitest.expect)(err).toBeInstanceOf(Error);
|
|
15
|
+
(0, import_vitest.expect)(String(err)).toMatch(/Line 1/);
|
|
16
|
+
(0, import_vitest.expect)(String(err)).toMatch(/length mismatch/);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|