@arkadia/data 0.1.7 → 0.1.8
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/.prettierrc +8 -0
- package/README.md +166 -112
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/Decoder.d.ts.map +1 -1
- package/dist/core/Decoder.js +123 -97
- package/dist/core/Decoder.js.map +1 -1
- package/dist/core/Encoder.d.ts +1 -2
- package/dist/core/Encoder.d.ts.map +1 -1
- package/dist/core/Encoder.js +74 -76
- package/dist/core/Encoder.js.map +1 -1
- package/dist/core/Parser.d.ts +1 -1
- package/dist/core/Parser.d.ts.map +1 -1
- package/dist/core/Parser.js +11 -11
- package/dist/core/Parser.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/models/Meta.d.ts +3 -2
- package/dist/models/Meta.d.ts.map +1 -1
- package/dist/models/Meta.js +3 -3
- package/dist/models/Meta.js.map +1 -1
- package/dist/models/Node.d.ts +4 -3
- package/dist/models/Node.d.ts.map +1 -1
- package/dist/models/Node.js +29 -23
- package/dist/models/Node.js.map +1 -1
- package/dist/models/Schema.d.ts.map +1 -1
- package/dist/models/Schema.js +27 -21
- package/dist/models/Schema.js.map +1 -1
- package/eslint.config.mjs +42 -0
- package/package.json +11 -1
- package/scripts/verify-build.js +95 -92
- package/src/config.ts +75 -75
- package/src/core/Decoder.ts +984 -922
- package/src/core/Encoder.ts +364 -371
- package/src/core/Parser.ts +112 -112
- package/src/index.ts +18 -20
- package/src/models/Meta.ts +107 -107
- package/src/models/Node.ts +190 -185
- package/src/models/Schema.ts +198 -193
- package/tests/00.meta.test.ts +19 -25
- package/tests/00.node.test.ts +40 -48
- package/tests/00.primitive.test.ts +121 -95
- package/tests/00.schema.test.ts +28 -35
- package/tests/01.schema.test.ts +42 -52
- package/tests/02.data.test.ts +69 -75
- package/tests/03.errors.test.ts +53 -55
- package/tests/04.list.test.ts +192 -193
- package/tests/05.record.test.ts +54 -56
- package/tests/06.meta.test.ts +393 -389
- package/tests/utils.ts +47 -44
- package/tsconfig.json +27 -29
- package/vitest.config.ts +1 -1
package/src/core/Encoder.ts
CHANGED
|
@@ -1,443 +1,436 @@
|
|
|
1
|
-
import { Node } from '../models/Node';
|
|
2
|
-
import { Schema } from '../models/Schema';
|
|
3
|
-
import { Meta } from '../models/Meta';
|
|
4
1
|
import { EncoderConfig } from '../config';
|
|
2
|
+
import { Meta } from '../models/Meta';
|
|
3
|
+
import { Node, Primitive } from '../models/Node';
|
|
4
|
+
import { Schema } from '../models/Schema';
|
|
5
5
|
|
|
6
6
|
class Colors {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
static RESET = '\x1b[0m';
|
|
8
|
+
static STRING = '\x1b[92m'; // green
|
|
9
|
+
static NUMBER = '\x1b[94m'; // blue
|
|
10
|
+
static BOOL = '\x1b[95m'; // magenta
|
|
11
|
+
static NULL = '\x1b[90m'; // gray
|
|
12
|
+
static TYPE = '\x1b[96m'; // cyan
|
|
13
|
+
static KEY = '\x1b[93m'; // yellow
|
|
14
|
+
static SCHEMA = '\x1b[91m'; // red (for @TypeName)
|
|
15
|
+
static TAG = '\x1b[91m';
|
|
16
|
+
static ATTR = '\x1b[93m';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export class Encoder {
|
|
20
|
-
|
|
20
|
+
private config: EncoderConfig;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
constructor(config: EncoderConfig) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
public encode(node: Node, indentLevel: number = 0, includeSchema: boolean = true): string {
|
|
31
|
-
const baseIndent = this.config.startIndent + indentLevel;
|
|
32
|
-
let schemaPrefix = "";
|
|
33
|
-
|
|
34
|
-
// 2. Prepare Schema Header (e.g. <test: string>)
|
|
35
|
-
const shouldRenderSchema = (
|
|
36
|
-
includeSchema &&
|
|
37
|
-
node.schema !== null &&
|
|
38
|
-
this.config.includeSchema
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
if (shouldRenderSchema) {
|
|
42
|
-
let sTxt = this.encodeSchema(node.schema!, baseIndent).trim();
|
|
43
|
-
|
|
44
|
-
// Jeśli wynik nie jest pusty i nie zaczyna się od < ani @, dodaj nawiasy < >
|
|
45
|
-
if (sTxt && !sTxt.startsWith('<') && !sTxt.startsWith('@')) {
|
|
46
|
-
sTxt = `<${sTxt}>`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (sTxt) {
|
|
50
|
-
if (this.config.compact) {
|
|
51
|
-
schemaPrefix = sTxt;
|
|
52
|
-
} else {
|
|
53
|
-
// Newline formatting for schema header
|
|
54
|
-
schemaPrefix = sTxt + "\n" + " ".repeat(baseIndent);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
26
|
+
// -------------------------------------------------------------
|
|
27
|
+
// PUBLIC ENTRY
|
|
28
|
+
// -------------------------------------------------------------
|
|
58
29
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
data = this.encodeList(node, baseIndent, false);
|
|
63
|
-
} else if (node.isPrimitive) {
|
|
64
|
-
data = this.encodePrimitiveNode(node);
|
|
65
|
-
} else if (node.isRecord) {
|
|
66
|
-
data = this.encodeRecord(node, baseIndent);
|
|
67
|
-
} else {
|
|
68
|
-
data = this.c("null", Colors.NULL);
|
|
69
|
-
}
|
|
30
|
+
public encode(node: Node, indentLevel: number = 0, includeSchema: boolean = true): string {
|
|
31
|
+
const baseIndent = this.config.startIndent + indentLevel;
|
|
32
|
+
let schemaPrefix = '';
|
|
70
33
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
34
|
+
// 2. Prepare Schema Header (e.g. <test: string>)
|
|
35
|
+
const shouldRenderSchema = includeSchema && node.schema !== null && this.config.includeSchema;
|
|
74
36
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const ind = " ".repeat(indent);
|
|
79
|
-
let prefix = "";
|
|
80
|
-
|
|
81
|
-
const pad = this.config.compact ? "" : " ";
|
|
82
|
-
|
|
83
|
-
// Avoid printing internal/default type names
|
|
84
|
-
if (
|
|
85
|
-
schema.typeName &&
|
|
86
|
-
schema.isRecord &&
|
|
87
|
-
!schema.isAny
|
|
88
|
-
) {
|
|
89
|
-
prefix = this.c(`@${schema.typeName}`, Colors.SCHEMA);
|
|
90
|
-
}
|
|
37
|
+
if (shouldRenderSchema) {
|
|
38
|
+
let sTxt = this.encodeSchema(node.schema!, baseIndent).trim();
|
|
91
39
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
40
|
+
// Jeśli wynik nie jest pusty i nie zaczyna się od < ani @, dodaj nawiasy < >
|
|
41
|
+
if (sTxt && !sTxt.startsWith('<') && !sTxt.startsWith('@')) {
|
|
42
|
+
sTxt = `<${sTxt}>`;
|
|
43
|
+
}
|
|
95
44
|
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
45
|
+
if (sTxt) {
|
|
46
|
+
if (this.config.compact) {
|
|
47
|
+
schemaPrefix = sTxt;
|
|
48
|
+
} else {
|
|
49
|
+
// Newline formatting for schema header
|
|
50
|
+
schemaPrefix = sTxt + '\n' + ' '.repeat(baseIndent);
|
|
102
51
|
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
103
54
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// We reuse the _record_fields logic but wrap in <[ ... ]>
|
|
116
|
-
const innerFields = this.encodeSchemaFields(schema.element);
|
|
117
|
-
|
|
118
|
-
// FIX: Use 'pad' variable to remove spaces in compact mode
|
|
119
|
-
return ind + prefix + "<" + pad + "[" + listMeta + innerFields + pad + "]" + pad + ">";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Standard List [Type]
|
|
123
|
-
const inner = this.encodeSchema(schema.element!, 0, false).trim();
|
|
124
|
-
return ind + "[" + listMeta + this.c(inner, Colors.TYPE) + "]";
|
|
125
|
-
}
|
|
55
|
+
// 3. Encode Data
|
|
56
|
+
let data = '';
|
|
57
|
+
if (node.isList) {
|
|
58
|
+
data = this.encodeList(node, baseIndent, false);
|
|
59
|
+
} else if (node.isPrimitive) {
|
|
60
|
+
data = this.encodePrimitiveNode(node);
|
|
61
|
+
} else if (node.isRecord) {
|
|
62
|
+
data = this.encodeRecord(node, baseIndent);
|
|
63
|
+
} else {
|
|
64
|
+
data = this.c('null', Colors.NULL);
|
|
65
|
+
}
|
|
126
66
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const recordMeta = includeMeta ? this.metaWrapped(schema) : "";
|
|
131
|
-
|
|
132
|
-
if (!schema.fields || schema.fields.length === 0) {
|
|
133
|
-
// If the record is generic (no fields, no specific type name, no meta),
|
|
134
|
-
// return an empty string to avoid printing "<>" or "<any>" before the "{...}".
|
|
135
|
-
if (!prefix && !recordMeta && schema.isAny) {
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
return ind + prefix + "<" + pad + recordMeta + "any" + pad + ">";
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Encode Fields
|
|
142
|
-
const innerFields = this.encodeSchemaFields(schema);
|
|
143
|
-
|
|
144
|
-
// FIX: Use 'pad' variable to remove spaces in compact mode
|
|
145
|
-
return ind + prefix + "<" + pad + recordMeta + innerFields + pad + ">";
|
|
146
|
-
}
|
|
67
|
+
// Final Assembly: Meta -> Schema -> Data
|
|
68
|
+
return `${schemaPrefix}${data}`;
|
|
69
|
+
}
|
|
147
70
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return ind + `<${meta}any>`;
|
|
151
|
-
}
|
|
71
|
+
public encodeSchema(schema: Schema, indent: number, includeMeta: boolean = true): string {
|
|
72
|
+
if (!schema) return '';
|
|
152
73
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const parts: string[] = [];
|
|
156
|
-
const pad = this.config.compact ? "" : " ";
|
|
157
|
-
|
|
158
|
-
for (const field of schema.fields) {
|
|
159
|
-
let str = "";
|
|
160
|
-
|
|
161
|
-
const metaPrefix = this.metaInline(field);
|
|
162
|
-
if (metaPrefix) {
|
|
163
|
-
str += metaPrefix + " ";
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 3. Field Name
|
|
167
|
-
str += this.c(field.name, Colors.KEY);
|
|
168
|
-
|
|
169
|
-
// 4. Field Type
|
|
170
|
-
const fieldType = this.encodeSchema(field, 0, false).trim();
|
|
171
|
-
|
|
172
|
-
// Logic to decide when to print the type signature
|
|
173
|
-
const isStructure = !field.isPrimitive;
|
|
174
|
-
const isExplicitPrimitive = (this.config.includeType && field.typeName !== "any");
|
|
175
|
-
|
|
176
|
-
if (fieldType && (isStructure || isExplicitPrimitive)) {
|
|
177
|
-
str += `:${pad}${this.c(fieldType, Colors.TYPE)}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
parts.push(str);
|
|
181
|
-
}
|
|
74
|
+
const ind = ' '.repeat(indent);
|
|
75
|
+
let prefix = '';
|
|
182
76
|
|
|
183
|
-
|
|
184
|
-
|
|
77
|
+
const pad = this.config.compact ? '' : ' ';
|
|
78
|
+
|
|
79
|
+
// Avoid printing internal/default type names
|
|
80
|
+
if (schema.typeName && schema.isRecord && !schema.isAny) {
|
|
81
|
+
prefix = this.c(`@${schema.typeName}`, Colors.SCHEMA);
|
|
185
82
|
}
|
|
186
83
|
|
|
84
|
+
// Przygotowanie meta (ale jeszcze nie użycie, bo w liście może się zmienić)
|
|
85
|
+
// W Pythonie: meta = self._meta_wrapped(schema) if include_meta else ""
|
|
86
|
+
// Tutaj obliczamy dynamicznie tam gdzie potrzeba, bo w liście modyfikujemy obiekt schema.
|
|
187
87
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!nodeSchema) return true;
|
|
195
|
-
|
|
196
|
-
// Check general kind
|
|
197
|
-
if (nodeSchema.kind !== expectedSchema.kind) return false;
|
|
198
|
-
|
|
199
|
-
// Check specific primitive type name (e.g. int vs string)
|
|
200
|
-
if (nodeSchema.isPrimitive && expectedSchema.isPrimitive) {
|
|
201
|
-
return nodeSchema.typeName === expectedSchema.typeName;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return true;
|
|
88
|
+
// --- PRIMITIVE ---
|
|
89
|
+
if (schema.isPrimitive) {
|
|
90
|
+
const metaPrefix = includeMeta ? this.metaInline(schema) : '';
|
|
91
|
+
// Python: return ind + ((meta_prefix + " ") if meta_prefix else "") + self._c(...)
|
|
92
|
+
const metaStr = metaPrefix ? metaPrefix + ' ' : '';
|
|
93
|
+
return ind + metaStr + this.c(schema.typeName, Colors.TYPE);
|
|
205
94
|
}
|
|
206
95
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
96
|
+
// --- LIST ---
|
|
97
|
+
// --- DEBUG ---
|
|
98
|
+
if (schema.isList) {
|
|
99
|
+
if (schema.element) {
|
|
100
|
+
schema.applyMeta(schema.element);
|
|
101
|
+
schema.element.clearMeta();
|
|
102
|
+
}
|
|
103
|
+
const listMeta = includeMeta ? this.metaWrapped(schema) : '';
|
|
104
|
+
|
|
105
|
+
// Special Case: List of Records < [ ... ] >
|
|
106
|
+
if (schema.element && schema.element.isRecord) {
|
|
107
|
+
// We reuse the _record_fields logic but wrap in <[ ... ]>
|
|
108
|
+
const innerFields = this.encodeSchemaFields(schema.element);
|
|
109
|
+
|
|
110
|
+
// FIX: Use 'pad' variable to remove spaces in compact mode
|
|
111
|
+
return ind + prefix + '<' + pad + '[' + listMeta + innerFields + pad + ']' + pad + '>';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Standard List [Type]
|
|
115
|
+
const inner = this.encodeSchema(schema.element!, 0, false).trim();
|
|
116
|
+
return ind + '[' + listMeta + this.c(inner, Colors.TYPE) + ']';
|
|
217
117
|
}
|
|
218
118
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
119
|
+
// --- RECORD ---
|
|
120
|
+
if (schema.isRecord) {
|
|
121
|
+
// Get Record-level meta (e.g. < / $ver=1 / ... >)
|
|
122
|
+
const recordMeta = includeMeta ? this.metaWrapped(schema) : '';
|
|
123
|
+
|
|
124
|
+
if (!schema.fields || schema.fields.length === 0) {
|
|
125
|
+
// If the record is generic (no fields, no specific type name, no meta),
|
|
126
|
+
// return an empty string to avoid printing "<>" or "<any>" before the "{...}".
|
|
127
|
+
if (!prefix && !recordMeta && schema.isAny) {
|
|
128
|
+
return '';
|
|
222
129
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
130
|
+
return ind + prefix + '<' + pad + recordMeta + 'any' + pad + '>';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Encode Fields
|
|
134
|
+
const innerFields = this.encodeSchemaFields(schema);
|
|
135
|
+
|
|
136
|
+
// FIX: Use 'pad' variable to remove spaces in compact mode
|
|
137
|
+
return ind + prefix + '<' + pad + recordMeta + innerFields + pad + '>';
|
|
228
138
|
}
|
|
229
139
|
|
|
140
|
+
// Fallback (ANY)
|
|
141
|
+
const meta = includeMeta ? this.metaWrapped(schema) : '';
|
|
142
|
+
return ind + `<${meta}any>`;
|
|
143
|
+
}
|
|
230
144
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
145
|
+
// --- Helper to deduplicate field encoding logic ---
|
|
146
|
+
private encodeSchemaFields(schema: Schema): string {
|
|
147
|
+
const parts: string[] = [];
|
|
148
|
+
const pad = this.config.compact ? '' : ' ';
|
|
234
149
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const pad = this.config.compact ? "" : " ";
|
|
150
|
+
for (const field of schema.fields) {
|
|
151
|
+
let str = '';
|
|
238
152
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
items.push(this.c(`/*${pad}${cleaned}${pad}*/`, Colors.NULL));
|
|
244
|
-
}
|
|
245
|
-
}
|
|
153
|
+
const metaPrefix = this.metaInline(field);
|
|
154
|
+
if (metaPrefix) {
|
|
155
|
+
str += metaPrefix + ' ';
|
|
156
|
+
}
|
|
246
157
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if ((obj as any).required) {
|
|
250
|
-
items.push(this.c("!required", Colors.TAG));
|
|
251
|
-
}
|
|
158
|
+
// 3. Field Name
|
|
159
|
+
str += this.c(field.name, Colors.KEY);
|
|
252
160
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const currentAttr = obj.attr || {};
|
|
256
|
-
for (const [k, v] of Object.entries(currentAttr)) {
|
|
257
|
-
const valStr = this.primitiveValue(v);
|
|
258
|
-
items.push(this.c(`$${k}=`, Colors.ATTR) + valStr);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const currentTags = obj.tags || [];
|
|
262
|
-
for (const t of currentTags) {
|
|
263
|
-
items.push(this.c(`#${t}`, Colors.TAG));
|
|
264
|
-
}
|
|
265
|
-
}
|
|
161
|
+
// 4. Field Type
|
|
162
|
+
const fieldType = this.encodeSchema(field, 0, false).trim();
|
|
266
163
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
164
|
+
// Logic to decide when to print the type signature
|
|
165
|
+
const isStructure = !field.isPrimitive;
|
|
166
|
+
const isExplicitPrimitive = this.config.includeType && field.typeName !== 'any';
|
|
270
167
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const wrappedContent = this.c(`/${pad}`, Colors.SCHEMA) + content + this.c(`${pad}/`, Colors.SCHEMA);
|
|
275
|
-
return this.config.compact ? wrappedContent + " " : " " + wrappedContent + " ";
|
|
276
|
-
}
|
|
168
|
+
if (fieldType && (isStructure || isExplicitPrimitive)) {
|
|
169
|
+
str += `:${pad}${this.c(fieldType, Colors.TYPE)}`;
|
|
170
|
+
}
|
|
277
171
|
|
|
278
|
-
|
|
279
|
-
return content;
|
|
172
|
+
parts.push(str);
|
|
280
173
|
}
|
|
281
174
|
|
|
282
|
-
|
|
283
|
-
|
|
175
|
+
const sep = `,${pad}`;
|
|
176
|
+
return parts.join(sep);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// -------------------------------------------------------------
|
|
180
|
+
// HELPER: SCHEMA COMPATIBILITY CHECK
|
|
181
|
+
// -------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
private schemasAreCompatible(nodeSchema: Schema | null, expectedSchema: Schema | null): boolean {
|
|
184
|
+
if (!expectedSchema || expectedSchema.isAny) return true;
|
|
185
|
+
if (!nodeSchema) return true;
|
|
186
|
+
|
|
187
|
+
// Check general kind
|
|
188
|
+
if (nodeSchema.kind !== expectedSchema.kind) return false;
|
|
189
|
+
|
|
190
|
+
// Check specific primitive type name (e.g. int vs string)
|
|
191
|
+
if (nodeSchema.isPrimitive && expectedSchema.isPrimitive) {
|
|
192
|
+
return nodeSchema.typeName === expectedSchema.typeName;
|
|
284
193
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private getTypeLabel(schema: Schema): string {
|
|
199
|
+
if (schema.isPrimitive) return schema.typeName || 'any';
|
|
200
|
+
if (schema.isList) {
|
|
201
|
+
const inner = schema.element ? this.getTypeLabel(schema.element) : 'any';
|
|
202
|
+
return `[${inner}]`;
|
|
288
203
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
204
|
+
if (schema.isRecord && schema.typeName && schema.typeName !== 'any') {
|
|
205
|
+
return schema.typeName;
|
|
206
|
+
}
|
|
207
|
+
return 'any';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private applyTypeTag(
|
|
211
|
+
valStr: string,
|
|
212
|
+
nodeSchema: Schema | null,
|
|
213
|
+
expectedSchema: Schema | null,
|
|
214
|
+
): string {
|
|
215
|
+
if (this.schemasAreCompatible(nodeSchema, expectedSchema)) {
|
|
216
|
+
return valStr;
|
|
296
217
|
}
|
|
297
218
|
|
|
298
|
-
|
|
299
|
-
|
|
219
|
+
// Mismatch detected -> Wrap with tag
|
|
220
|
+
const label = nodeSchema ? this.getTypeLabel(nodeSchema) : 'any';
|
|
221
|
+
const tag = this.c(`<${label}>`, Colors.SCHEMA);
|
|
222
|
+
return `${tag} ${valStr}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// -------------------------------------------------------------
|
|
226
|
+
// META AND COMMENTS (Unified Logic)
|
|
227
|
+
// -------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
private buildMetaString(obj: Meta, wrapped: boolean = false): string {
|
|
230
|
+
const items: string[] = [];
|
|
231
|
+
const pad = this.config.compact ? '' : ' ';
|
|
232
|
+
|
|
233
|
+
// 1. Comments
|
|
234
|
+
if (this.config.includeComments && obj.comments) {
|
|
235
|
+
for (const c of obj.comments) {
|
|
236
|
+
const cleaned = c.trim();
|
|
237
|
+
items.push(this.c(`/*${pad}${cleaned}${pad}*/`, Colors.NULL));
|
|
238
|
+
}
|
|
300
239
|
}
|
|
301
240
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return (innerMeta ? innerMeta + " " : "") + str;
|
|
241
|
+
// 2. Modifiers
|
|
242
|
+
if ((obj as Schema).required) {
|
|
243
|
+
items.push(this.c('!required', Colors.TAG));
|
|
306
244
|
}
|
|
307
245
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
246
|
+
// 3. Attributes & Tags
|
|
247
|
+
if (this.config.includeMeta) {
|
|
248
|
+
const currentAttr = obj.attr || {};
|
|
249
|
+
for (const [k, v] of Object.entries(currentAttr)) {
|
|
250
|
+
const valStr = this.primitiveValue(v);
|
|
251
|
+
items.push(this.c(`$${k}=`, Colors.ATTR) + valStr);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const currentTags = obj.tags || [];
|
|
255
|
+
for (const t of currentTags) {
|
|
256
|
+
items.push(this.c(`#${t}`, Colors.TAG));
|
|
257
|
+
}
|
|
314
258
|
}
|
|
315
259
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (this.config.escapeNewLines) {
|
|
319
|
-
content = this.escapeNewlines(content);
|
|
320
|
-
}
|
|
321
|
-
content = content.replace(/"/g, '\\"');
|
|
322
|
-
return this.c(`"${content}"`, Colors.STRING);
|
|
260
|
+
if (items.length === 0) {
|
|
261
|
+
return '';
|
|
323
262
|
}
|
|
324
263
|
|
|
325
|
-
|
|
326
|
-
// LIST
|
|
327
|
-
// -------------------------------------------------------------
|
|
264
|
+
const content = items.join(' ');
|
|
328
265
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
header += `${this.c('$size', Colors.KEY)}=${this.c(String(size), Colors.NUMBER)}${this.c(':', Colors.TYPE)}`;
|
|
334
|
-
}
|
|
335
|
-
return header;
|
|
266
|
+
if (wrapped) {
|
|
267
|
+
const wrappedContent =
|
|
268
|
+
this.c(`/${pad}`, Colors.SCHEMA) + content + this.c(`${pad}/`, Colors.SCHEMA);
|
|
269
|
+
return this.config.compact ? wrappedContent + ' ' : ' ' + wrappedContent + ' ';
|
|
336
270
|
}
|
|
337
271
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
272
|
+
// Inline: /* c1 */ !req $a=1
|
|
273
|
+
return content;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private metaInline(obj: Meta): string {
|
|
277
|
+
return this.buildMetaString(obj, false);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private metaWrapped(obj: Meta): string {
|
|
281
|
+
return this.buildMetaString(obj, true);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// -------------------------------------------------------------
|
|
285
|
+
// HELPERS
|
|
286
|
+
// -------------------------------------------------------------
|
|
287
|
+
private c(text: string, color: string): string {
|
|
288
|
+
if (!this.config.colorize) return text;
|
|
289
|
+
return `${color}${text}${Colors.RESET}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private encodePrimitiveNode(node: Node): string {
|
|
293
|
+
const innerMeta = this.metaInline(node);
|
|
294
|
+
const str = this.primitiveValue(node.value as Primitive);
|
|
295
|
+
return (innerMeta ? innerMeta + ' ' : '') + str;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private primitiveValue(v: Primitive): string {
|
|
299
|
+
if (typeof v === 'string') return this.encodeString(v);
|
|
300
|
+
if (v === true) return this.c('true', Colors.BOOL);
|
|
301
|
+
if (v === false) return this.c('false', Colors.BOOL);
|
|
302
|
+
if (v === null || v === undefined) return this.c('null', Colors.NULL);
|
|
303
|
+
return this.c(String(v), Colors.NUMBER);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private encodeString(v: string): string {
|
|
307
|
+
// 1. Escape backslashes first! (Critical step)
|
|
308
|
+
// We must turn a single literal \ into \\ before processing quotes.
|
|
309
|
+
let content = v.replace(/\\/g, '\\\\');
|
|
310
|
+
|
|
311
|
+
// 2. Escape quotes
|
|
312
|
+
// Turn " into \"
|
|
313
|
+
content = content.replace(/"/g, '\\"');
|
|
314
|
+
|
|
315
|
+
// 3. Handle newlines and control characters if configured
|
|
316
|
+
if (this.config.escapeNewLines) {
|
|
317
|
+
content = content.replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\t/g, '\\t');
|
|
342
318
|
}
|
|
343
319
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
320
|
+
// Wrap in quotes and colorize
|
|
321
|
+
return this.c(`"${content}"`, Colors.STRING);
|
|
322
|
+
}
|
|
347
323
|
|
|
348
|
-
|
|
324
|
+
// -------------------------------------------------------------
|
|
325
|
+
// LIST
|
|
326
|
+
// -------------------------------------------------------------
|
|
349
327
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
328
|
+
private listHeader(node: Node): string {
|
|
329
|
+
let header = '[';
|
|
330
|
+
if (this.config.includeArraySize) {
|
|
331
|
+
const size = node.elements.length;
|
|
332
|
+
header += `${this.c('$size', Colors.KEY)}=${this.c(String(size), Colors.NUMBER)}${this.c(':', Colors.TYPE)}`;
|
|
333
|
+
}
|
|
334
|
+
return header;
|
|
335
|
+
}
|
|
358
336
|
|
|
359
|
-
|
|
337
|
+
private joinLines(items: string[], sep: string): string {
|
|
338
|
+
if (this.config.compact) return items.join(sep);
|
|
339
|
+
if (sep === '\n') return items.join(sep);
|
|
340
|
+
return items.join(`${sep} `);
|
|
341
|
+
}
|
|
360
342
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
for (const el of node.elements) {
|
|
366
|
-
// IMPORTANT: We disable schema inclusion for elements to avoid duplication <...>
|
|
367
|
-
// unless types mismatch drastically.
|
|
368
|
-
let val = this.encode(el, 0, false).trim();
|
|
369
|
-
|
|
370
|
-
// Check compatibility & Inject override if needed
|
|
371
|
-
if (!this.schemasAreCompatible(el.schema, expectedChild)) {
|
|
372
|
-
const label = el.schema ? this.getTypeLabel(el.schema) : "any";
|
|
373
|
-
const tag = this.c(`<${label}>`, Colors.SCHEMA);
|
|
374
|
-
val = `${tag} ${val}`;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
items.push(val);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return ind + "[" + innerMeta + schemaHeader + items.join(",") + "]";
|
|
381
|
-
}
|
|
343
|
+
private encodeList(node: Node, indent: number, includeSchema: boolean = false): string {
|
|
344
|
+
const ind = ' '.repeat(indent);
|
|
345
|
+
const childIndent = indent + this.config.indent;
|
|
382
346
|
|
|
383
|
-
|
|
384
|
-
const header = this.listHeader(node);
|
|
385
|
-
const out: string[] = [ind + header];
|
|
347
|
+
const innerMeta = this.metaWrapped(node);
|
|
386
348
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
349
|
+
// 1. Generate Header Schema (if requested)
|
|
350
|
+
let schemaHeader = '';
|
|
351
|
+
if (includeSchema && node.schema && node.schema.element) {
|
|
352
|
+
schemaHeader = this.encodeSchema(node.schema.element, 0).trim();
|
|
353
|
+
}
|
|
354
|
+
if (schemaHeader) {
|
|
355
|
+
schemaHeader = schemaHeader + ' ';
|
|
356
|
+
}
|
|
390
357
|
|
|
391
|
-
|
|
392
|
-
out.push(" ".repeat(childIndent) + schemaHeader.trim());
|
|
393
|
-
}
|
|
358
|
+
const expectedChild = node.schema ? node.schema.element : null;
|
|
394
359
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
el, childIndent - this.config.startIndent, false
|
|
399
|
-
).trim();
|
|
360
|
+
// --- COMPACT MODE ---
|
|
361
|
+
if (this.config.compact) {
|
|
362
|
+
const items: string[] = [];
|
|
400
363
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
val = `${tag} ${val}`;
|
|
406
|
-
}
|
|
364
|
+
for (const el of node.elements) {
|
|
365
|
+
// IMPORTANT: We disable schema inclusion for elements to avoid duplication <...>
|
|
366
|
+
// unless types mismatch drastically.
|
|
367
|
+
let val = this.encode(el, 0, false).trim();
|
|
407
368
|
|
|
408
|
-
|
|
369
|
+
// Check compatibility & Inject override if needed
|
|
370
|
+
if (!this.schemasAreCompatible(el.schema, expectedChild)) {
|
|
371
|
+
const label = el.schema ? this.getTypeLabel(el.schema) : 'any';
|
|
372
|
+
const tag = this.c(`<${label}>`, Colors.SCHEMA);
|
|
373
|
+
val = `${tag} ${val}`;
|
|
409
374
|
}
|
|
410
375
|
|
|
411
|
-
|
|
412
|
-
|
|
376
|
+
items.push(val);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return ind + '[' + innerMeta + schemaHeader + items.join(',') + ']';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// --- PRETTY MODE ---
|
|
383
|
+
const header = this.listHeader(node);
|
|
384
|
+
const out: string[] = [ind + header];
|
|
385
|
+
|
|
386
|
+
if (innerMeta) {
|
|
387
|
+
out.push(' '.repeat(childIndent) + innerMeta.trim());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (schemaHeader) {
|
|
391
|
+
out.push(' '.repeat(childIndent) + schemaHeader.trim());
|
|
413
392
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
393
|
+
|
|
394
|
+
for (const el of node.elements) {
|
|
395
|
+
// IMPORTANT: Disable schema for children
|
|
396
|
+
let val = this.encode(el, childIndent - this.config.startIndent, false).trim();
|
|
397
|
+
|
|
398
|
+
// Check compatibility & Inject override if needed
|
|
399
|
+
if (!this.schemasAreCompatible(el.schema, expectedChild)) {
|
|
400
|
+
const label = el.schema ? this.getTypeLabel(el.schema) : 'any';
|
|
401
|
+
const tag = this.c(`<${label}>`, Colors.SCHEMA);
|
|
402
|
+
val = `${tag} ${val}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
out.push(' '.repeat(childIndent) + val);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
out.push(ind + ']');
|
|
409
|
+
return this.joinLines(out, '\n');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// -------------------------------------------------------------
|
|
413
|
+
// RECORD
|
|
414
|
+
// -------------------------------------------------------------
|
|
415
|
+
private encodeRecord(node: Node, indent: number): string {
|
|
416
|
+
const innerMeta = this.metaWrapped(node);
|
|
417
|
+
|
|
418
|
+
const parts: string[] = [];
|
|
419
|
+
if (node.schema.fields && node.schema.fields.length > 0) {
|
|
420
|
+
for (const fieldDef of node.schema.fields) {
|
|
421
|
+
const fieldNode = node.fields[fieldDef.name];
|
|
422
|
+
if (fieldNode) {
|
|
423
|
+
let val = this.encode(fieldNode, indent - this.config.startIndent, false).trim();
|
|
424
|
+
val = this.applyTypeTag(val, fieldNode.schema, fieldDef);
|
|
425
|
+
parts.push(val);
|
|
438
426
|
} else {
|
|
439
|
-
|
|
427
|
+
parts.push(this.c('null', Colors.NULL));
|
|
440
428
|
}
|
|
441
|
-
|
|
429
|
+
}
|
|
430
|
+
const sep = this.config.compact ? ',' : ', ';
|
|
431
|
+
return '(' + innerMeta + parts.join(sep) + ')';
|
|
432
|
+
} else {
|
|
433
|
+
return '(' + innerMeta + this.c('null', Colors.NULL) + ')';
|
|
442
434
|
}
|
|
443
|
-
}
|
|
435
|
+
}
|
|
436
|
+
}
|