ng-form-foundry-transformers 0.3.3 → 0.3.5

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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * libconfig AST → form schema and initial value.
3
+ *
4
+ * Inference works from the AST, not from plain data, because libconfig
5
+ * literals are statically typed: `20`, `20.0`, `true`, and `"x"` are four
6
+ * different types to the consuming C program, and the schema must reflect
7
+ * that. Rules the plain-data inference in `core/infer.ts` cannot express:
8
+ *
9
+ * - int leaves get `integer: true`; int64 values beyond 2^53 become string
10
+ * leaves constrained to integer digits (the `core/bigint.ts` carry).
11
+ * - A list of groups becomes a nodeGroupList whose item type is the union of
12
+ * the keys observed across entries; keys absent from at least one entry are
13
+ * marked `presence: true`, so building an entry that lacks them does not
14
+ * materialize null settings into the write-back.
15
+ * - Empty arrays/lists and heterogeneous lists have no honest element type,
16
+ * so they map to read-only string leaves carrying the raw source span —
17
+ * verbatim round-trip, no mistyping. A user-supplied JSON Schema (see the
18
+ * transformer options) replaces this inference entirely and makes typed
19
+ * empty collections editable.
20
+ */
21
+ import type { NodeGroup } from '../../core/schema';
22
+ import { type CfgGroup, type CfgList, type CfgValue } from './parser';
23
+ /** Infer the root NodeGroup for a parsed document. */
24
+ export declare function libconfigToNodeGroup(root: CfgGroup, source: string, name: string): NodeGroup;
25
+ /**
26
+ * The plain form value mirroring {@link libconfigToNodeGroup}'s schema.
27
+ *
28
+ * `emptyAsArrays` selects the empty-collection carry: the inferred schema
29
+ * renders an empty `[]`/`()` as a read-only verbatim string (no honest element
30
+ * type exists), while a user-supplied JSON Schema types it — so schema-driven
31
+ * extraction hands the form a real empty array instead.
32
+ */
33
+ export declare function extractValue(value: CfgValue, source: string, emptyAsArrays?: boolean): unknown;
34
+ type ListShape = 'groups' | 'scalars' | 'empty' | 'heterogeneous';
35
+ /** Classify a `( )` list: all groups, all same-family scalars, empty, or mixed. */
36
+ export declare function listShape(list: CfgList): ListShape;
37
+ /** The verbatim source slice of a node. */
38
+ export declare function raw(value: CfgValue, source: string): string;
39
+ export {};
@@ -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,5 +1,5 @@
1
1
  import { type Document } from 'yaml';
2
- import type { FormValue } from '../../core/schema';
2
+ import type { FormValue, Thesaurus } from '../../core/schema';
3
3
  import type { TransformResult } from '../../core/transformer';
4
4
  import { type JsonSchema, type JsonSchemaOptions } from '../../core/json-schema';
5
5
  /** Options for {@link yamlTransformer}'s `toSchema`. */
@@ -14,6 +14,13 @@ export interface YamlOptions {
14
14
  rootName?: string;
15
15
  /** Options forwarded to `jsonSchemaToNodeGroup` (`refDocuments`, `optionalPresence`). */
16
16
  schemaOptions?: JsonSchemaOptions;
17
+ /**
18
+ * Display metadata (`label`/`description`/choice `caseLabels`) injected into
19
+ * the produced schema, schema-driven or inferred alike — see
20
+ * `applyThesaurus`. Keys are plain identifier names, matched
21
+ * case-insensitively; never paths.
22
+ */
23
+ thesaurus?: Thesaurus;
17
24
  }
18
25
  /**
19
26
  * A YAML {@link Transformer}: turn a YAML config document into a form and write
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.yamlTransformer = void 0;
4
4
  const yaml_1 = require("yaml");
5
+ const thesaurus_1 = require("../../core/thesaurus");
5
6
  const infer_1 = require("../../core/infer");
6
7
  const json_schema_1 = require("../../core/json-schema");
7
8
  const bigint_1 = require("../../core/bigint");
@@ -29,7 +30,8 @@ exports.yamlTransformer = {
29
30
  const schema = options?.schema
30
31
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
31
32
  : (0, infer_1.inferNodeGroup)(data, options?.rootName);
32
- return { schema, binding: doc, initialValue: data };
33
+ const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
34
+ return { schema: labeled, binding: doc, initialValue: data };
33
35
  },
34
36
  toSource(value, binding) {
35
37
  const doc = binding.clone();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry-transformers",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
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",