ng-form-foundry-transformers 0.3.2 → 0.3.4
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 +64 -3
- package/dist/core/json-schema.d.ts +14 -2
- package/dist/core/json-schema.js +47 -17
- package/dist/core/schema.d.ts +10 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/transformers/json/json-transformer.d.ts +3 -1
- package/dist/transformers/json/json-transformer.js +1 -1
- package/dist/transformers/libconfig/index.d.ts +2 -0
- package/dist/transformers/libconfig/index.js +9 -0
- package/dist/transformers/libconfig/libconfig-transformer.d.ts +49 -0
- package/dist/transformers/libconfig/libconfig-transformer.js +36 -0
- package/dist/transformers/libconfig/parser.d.ts +90 -0
- package/dist/transformers/libconfig/parser.js +296 -0
- package/dist/transformers/libconfig/revert.d.ts +18 -0
- package/dist/transformers/libconfig/revert.js +243 -0
- package/dist/transformers/libconfig/schema.d.ts +39 -0
- package/dist/transformers/libconfig/schema.js +163 -0
- package/dist/transformers/yaml/yaml-transformer.d.ts +3 -1
- package/dist/transformers/yaml/yaml-transformer.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.libconfigToNodeGroup = libconfigToNodeGroup;
|
|
4
|
+
exports.extractValue = extractValue;
|
|
5
|
+
exports.listShape = listShape;
|
|
6
|
+
exports.raw = raw;
|
|
7
|
+
const parser_1 = require("./parser");
|
|
8
|
+
/** Digits only — the constraint for int64 values carried as strings. */
|
|
9
|
+
const INTEGER_STRING_PATTERN = '^[-+]?[0-9]+$';
|
|
10
|
+
/** Infer the root NodeGroup for a parsed document. */
|
|
11
|
+
function libconfigToNodeGroup(root, source, name) {
|
|
12
|
+
const group = groupToNode(root, source, name);
|
|
13
|
+
group.root = true;
|
|
14
|
+
return group;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The plain form value mirroring {@link libconfigToNodeGroup}'s schema.
|
|
18
|
+
*
|
|
19
|
+
* `emptyAsArrays` selects the empty-collection carry: the inferred schema
|
|
20
|
+
* renders an empty `[]`/`()` as a read-only verbatim string (no honest element
|
|
21
|
+
* type exists), while a user-supplied JSON Schema types it — so schema-driven
|
|
22
|
+
* extraction hands the form a real empty array instead.
|
|
23
|
+
*/
|
|
24
|
+
function extractValue(value, source, emptyAsArrays = false) {
|
|
25
|
+
switch (value.kind) {
|
|
26
|
+
case 'scalar':
|
|
27
|
+
return value.value;
|
|
28
|
+
case 'group': {
|
|
29
|
+
const out = Object.create(null);
|
|
30
|
+
for (const s of value.settings)
|
|
31
|
+
out[s.name] = extractValue(s.value, source, emptyAsArrays);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
case 'array':
|
|
35
|
+
if (value.elements.length === 0 && !emptyAsArrays)
|
|
36
|
+
return raw(value, source);
|
|
37
|
+
return arrayValue(value.elements);
|
|
38
|
+
case 'list': {
|
|
39
|
+
const shape = listShape(value);
|
|
40
|
+
if (shape === 'groups')
|
|
41
|
+
return value.elements.map((e) => extractValue(e, source, emptyAsArrays));
|
|
42
|
+
if (shape === 'scalars')
|
|
43
|
+
return arrayValue(value.elements);
|
|
44
|
+
if (shape === 'empty' && emptyAsArrays)
|
|
45
|
+
return [];
|
|
46
|
+
return raw(value, source); // empty or heterogeneous: the verbatim span
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function nodeFor(name, value, source) {
|
|
51
|
+
switch (value.kind) {
|
|
52
|
+
case 'scalar':
|
|
53
|
+
return scalarLeaf(name, value);
|
|
54
|
+
case 'group':
|
|
55
|
+
return groupToNode(value, source, name);
|
|
56
|
+
case 'array':
|
|
57
|
+
return listLeaf(name, value.elements);
|
|
58
|
+
case 'list': {
|
|
59
|
+
const shape = listShape(value);
|
|
60
|
+
if (shape === 'groups')
|
|
61
|
+
return groupListNode(name, value, source);
|
|
62
|
+
if (shape === 'scalars')
|
|
63
|
+
return listLeaf(name, value.elements);
|
|
64
|
+
return rawLeaf(name, shape === 'empty' ? 'empty collection' : 'heterogeneous list');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function groupToNode(group, source, name) {
|
|
69
|
+
const children = Object.create(null);
|
|
70
|
+
for (const s of group.settings)
|
|
71
|
+
children[s.name] = nodeFor(s.name, s.value, source);
|
|
72
|
+
return { kind: 'nodeGroup', name, children };
|
|
73
|
+
}
|
|
74
|
+
function scalarLeaf(name, scalar) {
|
|
75
|
+
switch (scalar.type) {
|
|
76
|
+
case 'bool':
|
|
77
|
+
return { kind: 'leaf', name, type: 'boolean' };
|
|
78
|
+
case 'string':
|
|
79
|
+
return { kind: 'leaf', name, type: 'string' };
|
|
80
|
+
case 'float':
|
|
81
|
+
return { kind: 'leaf', name, type: 'number' };
|
|
82
|
+
case 'int':
|
|
83
|
+
return { kind: 'leaf', name, type: 'number', integer: true };
|
|
84
|
+
case 'int64':
|
|
85
|
+
// Beyond 2^53 the value rides as an exact decimal string.
|
|
86
|
+
return typeof scalar.value === 'string'
|
|
87
|
+
? { kind: 'leaf', name, type: 'string', pattern: INTEGER_STRING_PATTERN }
|
|
88
|
+
: { kind: 'leaf', name, type: 'number', integer: true };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A homogeneous scalar collection → leafList. One int64 element beyond 2^53
|
|
93
|
+
* degrades the whole list to string carry: a leafList holds one scalar type,
|
|
94
|
+
* so consistency beats convenience (see {@link arrayValue}, which matches).
|
|
95
|
+
*/
|
|
96
|
+
function listLeaf(name, elements) {
|
|
97
|
+
if (elements.length === 0)
|
|
98
|
+
return rawLeaf(name, 'empty collection');
|
|
99
|
+
const f = (0, parser_1.family)(elements[0].type);
|
|
100
|
+
if (f === 'integer' && elements.some((e) => typeof e.value === 'string')) {
|
|
101
|
+
return { kind: 'leafList', name, type: 'string' };
|
|
102
|
+
}
|
|
103
|
+
return { kind: 'leafList', name, type: f === 'integer' || f === 'float' ? 'number' : f === 'bool' ? 'boolean' : 'string' };
|
|
104
|
+
}
|
|
105
|
+
function arrayValue(elements) {
|
|
106
|
+
const stringCarry = elements.length > 0 &&
|
|
107
|
+
(0, parser_1.family)(elements[0].type) === 'integer' &&
|
|
108
|
+
elements.some((e) => typeof e.value === 'string');
|
|
109
|
+
return elements.map((e) => (stringCarry ? String(e.value) : e.value));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A list of groups → nodeGroupList typed as the union of keys across entries;
|
|
113
|
+
* keys missing from at least one entry become presence fields, so entries
|
|
114
|
+
* lacking them build without the key instead of materializing nulls.
|
|
115
|
+
*/
|
|
116
|
+
function groupListNode(name, list, source) {
|
|
117
|
+
const groups = list.elements;
|
|
118
|
+
const children = Object.create(null);
|
|
119
|
+
const seenIn = Object.create(null);
|
|
120
|
+
for (const g of groups) {
|
|
121
|
+
for (const s of g.settings) {
|
|
122
|
+
if (!(s.name in children))
|
|
123
|
+
children[s.name] = nodeFor(s.name, s.value, source);
|
|
124
|
+
seenIn[s.name] = (seenIn[s.name] ?? 0) + 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const key of Object.keys(children)) {
|
|
128
|
+
if ((seenIn[key] ?? 0) < groups.length) {
|
|
129
|
+
const child = children[key];
|
|
130
|
+
if (child.kind === 'leaf' || child.kind === 'nodeGroup' || child.kind === 'map' || child.kind === 'choice') {
|
|
131
|
+
child.presence = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { kind: 'nodeGroupList', name, type: { kind: 'nodeGroup', name, children } };
|
|
136
|
+
}
|
|
137
|
+
/** Classify a `( )` list: all groups, all same-family scalars, empty, or mixed. */
|
|
138
|
+
function listShape(list) {
|
|
139
|
+
if (list.elements.length === 0)
|
|
140
|
+
return 'empty';
|
|
141
|
+
if (list.elements.every((e) => e.kind === 'group'))
|
|
142
|
+
return 'groups';
|
|
143
|
+
if (list.elements.every((e) => e.kind === 'scalar')) {
|
|
144
|
+
const families = new Set(list.elements.map((e) => (0, parser_1.family)(e.type)));
|
|
145
|
+
if (families.size === 1)
|
|
146
|
+
return 'scalars';
|
|
147
|
+
}
|
|
148
|
+
return 'heterogeneous';
|
|
149
|
+
}
|
|
150
|
+
/** Read-only leaf carrying the collection's verbatim source text. */
|
|
151
|
+
function rawLeaf(name, why) {
|
|
152
|
+
return {
|
|
153
|
+
kind: 'leaf',
|
|
154
|
+
name,
|
|
155
|
+
type: 'string',
|
|
156
|
+
readOnly: true,
|
|
157
|
+
description: `Shown verbatim (${why}): libconfig gives no element type to edit by. Provide a JSON Schema to make it editable.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** The verbatim source slice of a node. */
|
|
161
|
+
function raw(value, source) {
|
|
162
|
+
return source.slice(value.span.start, value.span.end);
|
|
163
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Document } from 'yaml';
|
|
2
2
|
import type { FormValue } from '../../core/schema';
|
|
3
3
|
import type { TransformResult } from '../../core/transformer';
|
|
4
|
-
import { type JsonSchema } from '../../core/json-schema';
|
|
4
|
+
import { type JsonSchema, type JsonSchemaOptions } from '../../core/json-schema';
|
|
5
5
|
/** Options for {@link yamlTransformer}'s `toSchema`. */
|
|
6
6
|
export interface YamlOptions {
|
|
7
7
|
/**
|
|
@@ -12,6 +12,8 @@ export interface YamlOptions {
|
|
|
12
12
|
schema?: JsonSchema;
|
|
13
13
|
/** Name for the root node group. Defaults to `__root__`. */
|
|
14
14
|
rootName?: string;
|
|
15
|
+
/** Options forwarded to `jsonSchemaToNodeGroup` (`refDocuments`, `optionalPresence`). */
|
|
16
|
+
schemaOptions?: JsonSchemaOptions;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* A YAML {@link Transformer}: turn a YAML config document into a form and write
|
|
@@ -27,7 +27,7 @@ exports.yamlTransformer = {
|
|
|
27
27
|
const doc = (0, yaml_1.parseDocument)(source, { intAsBigInt: true });
|
|
28
28
|
const data = (normalizeBigInts(doc.toJS()) ?? {});
|
|
29
29
|
const schema = options?.schema
|
|
30
|
-
? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName)
|
|
30
|
+
? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
|
|
31
31
|
: (0, infer_1.inferNodeGroup)(data, options?.rootName);
|
|
32
32
|
return { schema, binding: doc, initialValue: data };
|
|
33
33
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ng-form-foundry-transformers",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Framework-agnostic Node + TypeScript catalog of source-format transformers: turn a YANG model (and more) into an ng-form-foundry schema and revert the edited form value back to the source format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ng-form-foundry",
|