@effected/toml 0.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/LICENSE +21 -0
- package/README.md +140 -0
- package/Toml.js +165 -0
- package/TomlDateTime.js +147 -0
- package/TomlDiagnostic.js +106 -0
- package/TomlDocument.js +133 -0
- package/TomlEdit.js +52 -0
- package/TomlFormat.js +611 -0
- package/TomlNode.js +212 -0
- package/TomlVisitor.js +152 -0
- package/index.d.ts +897 -0
- package/index.js +10 -0
- package/internal/diagnostics.js +53 -0
- package/internal/limits.js +24 -0
- package/internal/parser.js +408 -0
- package/internal/scanner.js +493 -0
- package/internal/semantic.js +253 -0
- package/internal/stringifyValue.js +191 -0
- package/package.json +45 -0
- package/tsdoc-metadata.json +11 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { RawTomlError } from "./diagnostics.js";
|
|
2
|
+
import { TomlArray, TomlArrayTableHeader, TomlInlineTable, TomlTableHeader, TomlTrivia } from "../TomlNode.js";
|
|
3
|
+
|
|
4
|
+
//#region src/internal/semantic.ts
|
|
5
|
+
const makeNode = (kind, sectionId = 0) => ({
|
|
6
|
+
kind,
|
|
7
|
+
table: /* @__PURE__ */ new Map(),
|
|
8
|
+
elements: [],
|
|
9
|
+
sectionId
|
|
10
|
+
});
|
|
11
|
+
const raise = (code, message, key) => {
|
|
12
|
+
throw new RawTomlError({
|
|
13
|
+
code,
|
|
14
|
+
message,
|
|
15
|
+
offset: key.offset,
|
|
16
|
+
length: key.length
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Header navigation (G8 rules 1 and 2, shared prefix): walk every segment but
|
|
21
|
+
* the last, creating `table-implicit` for absent names, passing through
|
|
22
|
+
* tables (dotted included — the corpus deviation) and descending into the
|
|
23
|
+
* last element of an array of tables.
|
|
24
|
+
*/
|
|
25
|
+
const navigateHeaderPrefix = (root, keyPath) => {
|
|
26
|
+
let current = root;
|
|
27
|
+
for (let i = 0; i < keyPath.length - 1; i++) {
|
|
28
|
+
const key = keyPath[i];
|
|
29
|
+
const existing = current.table.get(key.value);
|
|
30
|
+
if (existing === void 0) {
|
|
31
|
+
const child = makeNode("table-implicit");
|
|
32
|
+
current.table.set(key.value, child);
|
|
33
|
+
current = child;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
switch (existing.kind) {
|
|
37
|
+
case "table-explicit":
|
|
38
|
+
case "table-implicit":
|
|
39
|
+
case "table-dotted":
|
|
40
|
+
current = existing;
|
|
41
|
+
break;
|
|
42
|
+
case "array-tables":
|
|
43
|
+
current = existing.elements[existing.elements.length - 1];
|
|
44
|
+
break;
|
|
45
|
+
case "inline": return raise("InlineTableExtended", `inline table "${key.value}" cannot be extended`, key);
|
|
46
|
+
case "static-array": return raise("ArrayOfTablesConflict", `"${key.value}" is a static array, not an array of tables`, key);
|
|
47
|
+
case "value": return raise("TableRedefined", `"${key.value}" is already defined as a value`, key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
};
|
|
52
|
+
/** G8 rule 1, final segment: create `table-explicit`, promote `table-implicit`, or error. */
|
|
53
|
+
const openTable = (root, keyPath) => {
|
|
54
|
+
const parent = navigateHeaderPrefix(root, keyPath);
|
|
55
|
+
const key = keyPath[keyPath.length - 1];
|
|
56
|
+
const existing = parent.table.get(key.value);
|
|
57
|
+
if (existing === void 0) {
|
|
58
|
+
const node = makeNode("table-explicit");
|
|
59
|
+
parent.table.set(key.value, node);
|
|
60
|
+
return node;
|
|
61
|
+
}
|
|
62
|
+
if (existing.kind === "table-implicit") {
|
|
63
|
+
existing.kind = "table-explicit";
|
|
64
|
+
return existing;
|
|
65
|
+
}
|
|
66
|
+
return raise("TableRedefined", `table "${key.value}" is already defined`, key);
|
|
67
|
+
};
|
|
68
|
+
/** G8 rule 2, final segment: create the array and its first element, append to an existing one, or error. */
|
|
69
|
+
const openArrayTable = (root, keyPath) => {
|
|
70
|
+
const parent = navigateHeaderPrefix(root, keyPath);
|
|
71
|
+
const key = keyPath[keyPath.length - 1];
|
|
72
|
+
const existing = parent.table.get(key.value);
|
|
73
|
+
if (existing === void 0) {
|
|
74
|
+
const array = makeNode("array-tables");
|
|
75
|
+
const element = makeNode("table-explicit");
|
|
76
|
+
array.elements.push(element);
|
|
77
|
+
parent.table.set(key.value, array);
|
|
78
|
+
return {
|
|
79
|
+
element,
|
|
80
|
+
index: 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (existing.kind === "array-tables") {
|
|
84
|
+
const element = makeNode("table-explicit");
|
|
85
|
+
existing.elements.push(element);
|
|
86
|
+
return {
|
|
87
|
+
element,
|
|
88
|
+
index: existing.elements.length - 1
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return raise("ArrayOfTablesConflict", `"${key.value}" cannot be redefined as an array of tables`, key);
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* G8 rule 3: a (possibly dotted) key assignment scoped to `table` under
|
|
95
|
+
* `sectionId`. Intermediates must be absent (create `table-dotted`) or
|
|
96
|
+
* `table-dotted` from the same section; the final key must be absent.
|
|
97
|
+
*/
|
|
98
|
+
const assignEntry = (table, sectionId, keyPath, value, context) => {
|
|
99
|
+
let current = table;
|
|
100
|
+
for (let i = 0; i < keyPath.length - 1; i++) {
|
|
101
|
+
const key = keyPath[i];
|
|
102
|
+
const existing = current.table.get(key.value);
|
|
103
|
+
if (existing === void 0) {
|
|
104
|
+
const child = makeNode("table-dotted", sectionId);
|
|
105
|
+
current.table.set(key.value, child);
|
|
106
|
+
current = child;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (existing.kind === "table-dotted" && existing.sectionId === sectionId) {
|
|
110
|
+
current = existing;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (existing.kind === "inline") raise("InlineTableExtended", `inline table "${key.value}" cannot be extended`, key);
|
|
114
|
+
raise("DottedKeyConflict", `dotted key cannot extend "${key.value}"`, key);
|
|
115
|
+
}
|
|
116
|
+
const key = keyPath[keyPath.length - 1];
|
|
117
|
+
if (current.table.has(key.value)) raise("DuplicateKey", `duplicate key "${key.value}"`, key);
|
|
118
|
+
current.table.set(key.value, nodeForValue(value, context));
|
|
119
|
+
};
|
|
120
|
+
/** The provenance node for an assigned value; validates inline tables (arrays included) on the way. */
|
|
121
|
+
const nodeForValue = (value, context) => {
|
|
122
|
+
if (value instanceof TomlInlineTable) return inlineNode(value, context);
|
|
123
|
+
if (value instanceof TomlArray) {
|
|
124
|
+
for (const item of value.items) checkArrayItem(item, context);
|
|
125
|
+
return makeNode("static-array");
|
|
126
|
+
}
|
|
127
|
+
return makeNode("value");
|
|
128
|
+
};
|
|
129
|
+
/** Inline tables hide anywhere inside a static array; validate them all. Depth is parser-capped. */
|
|
130
|
+
const checkArrayItem = (item, context) => {
|
|
131
|
+
if (item instanceof TomlInlineTable) {
|
|
132
|
+
inlineNode(item, context);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (item instanceof TomlArray) for (const inner of item.items) checkArrayItem(inner, context);
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* G8 rule 4: build an inline table's own scope (dotted keys follow rule 3
|
|
139
|
+
* inside it), then freeze it — the returned node's `inline` kind makes every
|
|
140
|
+
* later navigation through it an `InlineTableExtended` error.
|
|
141
|
+
*/
|
|
142
|
+
const inlineNode = (node, context) => {
|
|
143
|
+
const scopeId = context.nextId++;
|
|
144
|
+
const sem = makeNode("table-dotted", scopeId);
|
|
145
|
+
for (const entry of node.entries) assignEntry(sem, scopeId, entry.keyPath, entry.value, context);
|
|
146
|
+
sem.kind = "inline";
|
|
147
|
+
return sem;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Walk the expressions through the G8 state machine, firing the visitor after
|
|
151
|
+
* each expression validates; throws `RawTomlError` at the first violation.
|
|
152
|
+
*/
|
|
153
|
+
const analyze = (expressions, visitor) => {
|
|
154
|
+
const root = makeNode("table-explicit");
|
|
155
|
+
const context = { nextId: 1 };
|
|
156
|
+
let currentTable = root;
|
|
157
|
+
let currentSectionId = 0;
|
|
158
|
+
let currentPrefix = [];
|
|
159
|
+
visitor?.onTableStart?.([], void 0);
|
|
160
|
+
for (const expression of expressions) {
|
|
161
|
+
if (expression instanceof TomlTrivia) continue;
|
|
162
|
+
if (expression instanceof TomlTableHeader) {
|
|
163
|
+
currentTable = openTable(root, expression.keyPath);
|
|
164
|
+
currentSectionId = context.nextId++;
|
|
165
|
+
currentPrefix = expression.keyPath.map((key) => key.value);
|
|
166
|
+
visitor?.onTableStart?.(currentPrefix, expression);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (expression instanceof TomlArrayTableHeader) {
|
|
170
|
+
const { element, index } = openArrayTable(root, expression.keyPath);
|
|
171
|
+
currentTable = element;
|
|
172
|
+
currentSectionId = context.nextId++;
|
|
173
|
+
currentPrefix = expression.keyPath.map((key) => key.value);
|
|
174
|
+
visitor?.onArrayTableStart?.(currentPrefix, index, expression);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
assignEntry(currentTable, currentSectionId, expression.keyPath, expression.value, context);
|
|
178
|
+
visitor?.onKeyValue?.([...currentPrefix, ...expression.keyPath.map((key) => key.value)], expression);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
/** Set a key as an own data property — `__proto__` included (yaml/jsonc precedent). */
|
|
182
|
+
const setOwnProperty = (target, key, value) => {
|
|
183
|
+
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
184
|
+
value,
|
|
185
|
+
writable: true,
|
|
186
|
+
enumerable: true,
|
|
187
|
+
configurable: true
|
|
188
|
+
});
|
|
189
|
+
else target[key] = value;
|
|
190
|
+
};
|
|
191
|
+
/** Own-property read that never leaks through the prototype chain (`__proto__` included). */
|
|
192
|
+
const getOwnProperty = (target, key) => Object.hasOwn(target, key) ? target[key] : void 0;
|
|
193
|
+
/**
|
|
194
|
+
* Navigate `path` in the output tree, creating plain objects for absent names
|
|
195
|
+
* and descending into the LAST element of arrays (mirrors the array-of-tables
|
|
196
|
+
* rule — `analyze` has already proven every step legal). Iterative.
|
|
197
|
+
*/
|
|
198
|
+
const navigateOutput = (target, path) => {
|
|
199
|
+
let current = target;
|
|
200
|
+
for (const segment of path) {
|
|
201
|
+
let child = getOwnProperty(current, segment);
|
|
202
|
+
if (child === void 0) {
|
|
203
|
+
child = {};
|
|
204
|
+
setOwnProperty(current, segment, child);
|
|
205
|
+
}
|
|
206
|
+
if (Array.isArray(child)) child = child[child.length - 1];
|
|
207
|
+
current = child;
|
|
208
|
+
}
|
|
209
|
+
return current;
|
|
210
|
+
};
|
|
211
|
+
/** Materialize a CST value node into a plain value. Recursion is parser-depth-capped. */
|
|
212
|
+
const materialize = (value) => {
|
|
213
|
+
if (value instanceof TomlArray) return value.items.map(materialize);
|
|
214
|
+
if (value instanceof TomlInlineTable) {
|
|
215
|
+
const output = {};
|
|
216
|
+
for (const entry of value.entries) {
|
|
217
|
+
const parent = navigateOutput(output, entry.keyPath.slice(0, -1).map((key) => key.value));
|
|
218
|
+
setOwnProperty(parent, entry.keyPath[entry.keyPath.length - 1].value, materialize(entry.value));
|
|
219
|
+
}
|
|
220
|
+
return output;
|
|
221
|
+
}
|
|
222
|
+
return value.value;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* `analyze` + plain-value construction in a single pass: the value builder is
|
|
226
|
+
* the default visitor riding the same walk. Scalars materialize to their
|
|
227
|
+
* decoded values (`TomlInteger` → number | bigint, `TomlDateTimeLiteral` →
|
|
228
|
+
* its date-time class instance), arrays to plain arrays, tables and inline
|
|
229
|
+
* tables to plain objects with `__proto__` as an own data property.
|
|
230
|
+
*/
|
|
231
|
+
const buildValue = (expressions) => {
|
|
232
|
+
const result = {};
|
|
233
|
+
analyze(expressions, {
|
|
234
|
+
onTableStart: (path, _header) => {
|
|
235
|
+
navigateOutput(result, path);
|
|
236
|
+
},
|
|
237
|
+
onArrayTableStart: (path, _index, _header) => {
|
|
238
|
+
const parent = navigateOutput(result, path.slice(0, -1));
|
|
239
|
+
const key = path[path.length - 1];
|
|
240
|
+
const existing = getOwnProperty(parent, key);
|
|
241
|
+
if (Array.isArray(existing)) existing.push({});
|
|
242
|
+
else setOwnProperty(parent, key, [{}]);
|
|
243
|
+
},
|
|
244
|
+
onKeyValue: (path, expr) => {
|
|
245
|
+
const parent = navigateOutput(result, path.slice(0, -1));
|
|
246
|
+
setOwnProperty(parent, path[path.length - 1], materialize(expr.value));
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
return result;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
//#endregion
|
|
253
|
+
export { analyze, buildValue };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { RawTomlError } from "./diagnostics.js";
|
|
2
|
+
import { GuardExceeded } from "./limits.js";
|
|
3
|
+
import { TomlLocalDate, TomlLocalDateTime, TomlLocalTime, TomlOffsetDateTime } from "../TomlDateTime.js";
|
|
4
|
+
|
|
5
|
+
//#region src/internal/stringifyValue.ts
|
|
6
|
+
const INT64_MIN = -(2n ** 63n);
|
|
7
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
8
|
+
const INT64_FLOAT_BOUND = 2 ** 63;
|
|
9
|
+
const BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
10
|
+
const raise = (code, message) => {
|
|
11
|
+
throw new RawTomlError({
|
|
12
|
+
code,
|
|
13
|
+
message,
|
|
14
|
+
offset: 0,
|
|
15
|
+
length: 0
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const guardDepth = (depth) => {
|
|
19
|
+
if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, 0);
|
|
20
|
+
};
|
|
21
|
+
/** Render an error path for diagnostics: dotted keys, bracketed indices. */
|
|
22
|
+
const renderPath = (path) => {
|
|
23
|
+
if (path.length === 0) return "(root)";
|
|
24
|
+
let out = "";
|
|
25
|
+
for (const segment of path) if (typeof segment === "number") out += `[${segment}]`;
|
|
26
|
+
else out += out === "" ? renderKey(segment) : `.${renderKey(segment)}`;
|
|
27
|
+
return out;
|
|
28
|
+
};
|
|
29
|
+
/** The JS type name for an UnsupportedValue message. */
|
|
30
|
+
const jsTypeName = (value) => {
|
|
31
|
+
if (value === null) return "null";
|
|
32
|
+
if (typeof value !== "object") return typeof value;
|
|
33
|
+
const name = Object.getPrototypeOf(value)?.constructor?.name;
|
|
34
|
+
return typeof name === "string" && name.length > 0 ? name : "object";
|
|
35
|
+
};
|
|
36
|
+
/** A basic single-line string: `"` `\` and control characters escaped. */
|
|
37
|
+
const renderString = (value) => {
|
|
38
|
+
let out = "\"";
|
|
39
|
+
for (let i = 0; i < value.length; i++) {
|
|
40
|
+
const code = value.charCodeAt(i);
|
|
41
|
+
if (code === 34) out += "\\\"";
|
|
42
|
+
else if (code === 92) out += "\\\\";
|
|
43
|
+
else if (code === 8) out += "\\b";
|
|
44
|
+
else if (code === 9) out += "\\t";
|
|
45
|
+
else if (code === 10) out += "\\n";
|
|
46
|
+
else if (code === 12) out += "\\f";
|
|
47
|
+
else if (code === 13) out += "\\r";
|
|
48
|
+
else if (code < 32 || code === 127) out += `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
49
|
+
else out += value[i];
|
|
50
|
+
}
|
|
51
|
+
return `${out}"`;
|
|
52
|
+
};
|
|
53
|
+
/** A key: bare when it matches the bare-key grammar, else basic-quoted. */
|
|
54
|
+
const renderKey = (key) => BARE_KEY.test(key) ? key : renderString(key);
|
|
55
|
+
/** A dotted header path, quoting per-segment by the key rule. */
|
|
56
|
+
const renderHeaderPath = (path) => path.map(renderKey).join(".");
|
|
57
|
+
/**
|
|
58
|
+
* A `number`: integral values inside the int64 range emit as decimal integers
|
|
59
|
+
* (a JS `1.0` is indistinguishable from `1` — every JS emitter shares this
|
|
60
|
+
* divergence); `-0` emits as `-0.0`; integral values outside the int64 range
|
|
61
|
+
* emit as floats so they round-trip instead of overflowing at parse time;
|
|
62
|
+
* non-integral values emit as the shortest round-trip decimal with a
|
|
63
|
+
* guaranteed `.` or `e`; the specials emit as `inf`/`-inf`/`nan`.
|
|
64
|
+
*/
|
|
65
|
+
const renderNumber = (value) => {
|
|
66
|
+
if (Number.isNaN(value)) return "nan";
|
|
67
|
+
if (value === Number.POSITIVE_INFINITY) return "inf";
|
|
68
|
+
if (value === Number.NEGATIVE_INFINITY) return "-inf";
|
|
69
|
+
if (Object.is(value, -0)) return "-0.0";
|
|
70
|
+
if (Number.isInteger(value) && value >= -INT64_FLOAT_BOUND && value < INT64_FLOAT_BOUND) return String(value);
|
|
71
|
+
const text = String(value);
|
|
72
|
+
return /[.eE]/.test(text) ? text : `${text}.0`;
|
|
73
|
+
};
|
|
74
|
+
const isTomlDateTime = (value) => value instanceof TomlOffsetDateTime || value instanceof TomlLocalDateTime || value instanceof TomlLocalDate || value instanceof TomlLocalTime;
|
|
75
|
+
/** Plain objects only (null-prototype included) — never arrays or class instances. */
|
|
76
|
+
const isPlainObject = (value) => {
|
|
77
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
78
|
+
const proto = Object.getPrototypeOf(value);
|
|
79
|
+
return proto === Object.prototype || proto === null;
|
|
80
|
+
};
|
|
81
|
+
/** Scalar rendering; `undefined` means "not a scalar" (arrays/objects/rejects). */
|
|
82
|
+
const renderScalar = (value, path) => {
|
|
83
|
+
switch (typeof value) {
|
|
84
|
+
case "string": return renderString(value);
|
|
85
|
+
case "boolean": return value ? "true" : "false";
|
|
86
|
+
case "number": return renderNumber(value);
|
|
87
|
+
case "bigint":
|
|
88
|
+
if (value < INT64_MIN || value > INT64_MAX) raise("IntegerOutOfRange", `integer ${value} at ${renderPath(path)} is outside the 64-bit signed range`);
|
|
89
|
+
return value.toString();
|
|
90
|
+
default:
|
|
91
|
+
if (isTomlDateTime(value)) return value.toString();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const checkCircular = (value, ancestors, path) => {
|
|
96
|
+
if (ancestors.has(value)) raise("CircularReference", `circular reference at ${renderPath(path)}`);
|
|
97
|
+
};
|
|
98
|
+
/** One value as an inline fragment: scalars, inline arrays, inline tables. */
|
|
99
|
+
const renderInline = (value, path, depth, ancestors) => {
|
|
100
|
+
const scalar = renderScalar(value, path);
|
|
101
|
+
if (scalar !== void 0) return scalar;
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
guardDepth(depth);
|
|
104
|
+
checkCircular(value, ancestors, path);
|
|
105
|
+
ancestors.add(value);
|
|
106
|
+
const items = value.map((item, index) => renderInline(item, [...path, index], depth + 1, ancestors));
|
|
107
|
+
ancestors.delete(value);
|
|
108
|
+
return `[${items.join(", ")}]`;
|
|
109
|
+
}
|
|
110
|
+
if (isPlainObject(value)) {
|
|
111
|
+
guardDepth(depth);
|
|
112
|
+
checkCircular(value, ancestors, path);
|
|
113
|
+
ancestors.add(value);
|
|
114
|
+
const parts = Object.keys(value).map((key) => `${renderKey(key)} = ${renderInline(value[key], [...path, key], depth + 1, ancestors)}`);
|
|
115
|
+
ancestors.delete(value);
|
|
116
|
+
return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
117
|
+
}
|
|
118
|
+
return raise("UnsupportedValue", `unsupported ${jsTypeName(value)} value at ${renderPath(path)}`);
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Render a single value as an inline TOML fragment (scalars, arrays and
|
|
122
|
+
* objects all inline). The single-value seam the document modify entry point
|
|
123
|
+
* rides on.
|
|
124
|
+
*/
|
|
125
|
+
const renderInlineValue = (value) => renderInline(value, [], 0, /* @__PURE__ */ new Set());
|
|
126
|
+
const classify = (table) => {
|
|
127
|
+
const pairs = [];
|
|
128
|
+
const tables = [];
|
|
129
|
+
const arrayTables = [];
|
|
130
|
+
for (const key of Object.keys(table)) {
|
|
131
|
+
const value = table[key];
|
|
132
|
+
if (isPlainObject(value)) tables.push(key);
|
|
133
|
+
else if (Array.isArray(value) && value.length > 0 && value.every(isPlainObject)) arrayTables.push(key);
|
|
134
|
+
else pairs.push(key);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
pairs,
|
|
138
|
+
tables,
|
|
139
|
+
arrayTables
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
/** Push a header line, preceded by a blank line except at document start. */
|
|
143
|
+
const pushHeader = (lines, header) => {
|
|
144
|
+
if (lines.length > 0) lines.push("");
|
|
145
|
+
lines.push(header);
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Emit one table's body: pairs, then sub-table sections, then
|
|
149
|
+
* array-of-tables sections. `headerPath` carries the dotted section path
|
|
150
|
+
* (strings only); `errorPath` additionally carries array indices.
|
|
151
|
+
*/
|
|
152
|
+
const emitTable = (table, headerPath, errorPath, lines, depth, ancestors) => {
|
|
153
|
+
guardDepth(depth);
|
|
154
|
+
checkCircular(table, ancestors, errorPath);
|
|
155
|
+
ancestors.add(table);
|
|
156
|
+
const { pairs, tables, arrayTables } = classify(table);
|
|
157
|
+
for (const key of pairs) lines.push(`${renderKey(key)} = ${renderInline(table[key], [...errorPath, key], depth + 1, ancestors)}`);
|
|
158
|
+
for (const key of tables) {
|
|
159
|
+
pushHeader(lines, `[${renderHeaderPath([...headerPath, key])}]`);
|
|
160
|
+
emitTable(table[key], [...headerPath, key], [...errorPath, key], lines, depth + 1, ancestors);
|
|
161
|
+
}
|
|
162
|
+
for (const key of arrayTables) {
|
|
163
|
+
const array = table[key];
|
|
164
|
+
checkCircular(array, ancestors, [...errorPath, key]);
|
|
165
|
+
ancestors.add(array);
|
|
166
|
+
for (let index = 0; index < array.length; index++) {
|
|
167
|
+
pushHeader(lines, `[[${renderHeaderPath([...headerPath, key])}]]`);
|
|
168
|
+
emitTable(array[index], [...headerPath, key], [
|
|
169
|
+
...errorPath,
|
|
170
|
+
key,
|
|
171
|
+
index
|
|
172
|
+
], lines, depth + 1, ancestors);
|
|
173
|
+
}
|
|
174
|
+
ancestors.delete(array);
|
|
175
|
+
}
|
|
176
|
+
ancestors.delete(table);
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Stringify a plain value as a canonical TOML document. The root must be a
|
|
180
|
+
* plain object (a TOML document is a table); an empty root emits the empty
|
|
181
|
+
* string, anything else ends with `newline`.
|
|
182
|
+
*/
|
|
183
|
+
const stringifyValue = (value, newline) => {
|
|
184
|
+
if (!isPlainObject(value)) return raise("UnsupportedValue", `unsupported ${jsTypeName(value)} value at ${renderPath([])} — a TOML document is a table`);
|
|
185
|
+
const lines = [];
|
|
186
|
+
emitTable(value, [], [], lines, 0, /* @__PURE__ */ new Set());
|
|
187
|
+
return lines.length === 0 ? "" : `${lines.join(newline)}${newline}`;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
export { renderInlineValue, renderKey, stringifyValue };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effected/toml",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "TOML 1.0.0 parsing, editing and formatting as Effect schemas: typed diagnostics, a lossless CST and first-class date-time values.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"toml",
|
|
8
|
+
"parser",
|
|
9
|
+
"config",
|
|
10
|
+
"cst",
|
|
11
|
+
"effect",
|
|
12
|
+
"schema",
|
|
13
|
+
"effected"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/spencerbeggs/effected/tree/main/packages/toml#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/spencerbeggs/effected/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/spencerbeggs/effected.git",
|
|
22
|
+
"directory": "packages/toml"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "C. Spencer Beggs",
|
|
27
|
+
"email": "spencer@beggs.codes",
|
|
28
|
+
"url": "https://spencerbeg.gs"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./index.d.ts",
|
|
35
|
+
"import": "./index.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"effect": "4.0.0-beta.98"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=24.11.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.9"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|