ng-form-foundry-transformers 0.4.1 → 0.5.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 CHANGED
@@ -16,7 +16,7 @@ or a CLI just as easily.
16
16
  | `yang` | YANG model (via an engine) | RFC 7951 instance data | available |
17
17
  | `yaml` | YAML config (optionally a JSON Schema) | YAML (comments preserved) | available |
18
18
  | `json` | JSON config (optionally a JSON Schema) | JSON (indent preserved) | available |
19
- | `libconfig` | libconfig document (optionally a JSON Schema) | libconfig (comments **and scalar types** preserved) | **beta** — warns on first use |
19
+ | `libconfig` | libconfig document (optionally a JSON Schema) | libconfig (comments **and scalar types** preserved) | available |
20
20
 
21
21
  The YAML and JSON transformers share the same format-agnostic form builders in
22
22
  `core` (`inferNodeGroup`, `jsonSchemaToNodeGroup`) — a JSON Schema is an *option*
@@ -169,12 +169,10 @@ const { schema, binding, initialValue } = jsonTransformer.toSchema(configText);
169
169
  const out = jsonTransformer.toSource({ ...initialValue, replicas: 5 }, binding);
170
170
  ```
171
171
 
172
- ## libconfig transformer (beta)
172
+ ## libconfig transformer
173
173
 
174
174
  Edit a **libconfig document** — the `.cfg`/`.conf` format of srsRAN, OAI, and
175
175
  other C/C++ software using [libconfig](https://hyperrealm.github.io/libconfig/).
176
- **Beta**: it logs a one-time warning on first use; diff `toSource` output
177
- against the original before deploying a write-back.
178
176
 
179
177
  ```ts
180
178
  import { libconfigTransformer } from 'ng-form-foundry-transformers';
@@ -294,6 +292,14 @@ npm test # node:test on the compiled output (no Python needed)
294
292
 
295
293
  ## Status
296
294
 
295
+ `0.5.0` — the **libconfig transformer is stable**: the one-time beta warning
296
+ is gone (and with it the `resetLibconfigBetaWarning` helper), and the format's
297
+ guarantees and known limitations are documented in the transformers guide.
298
+ Non-decimal integer literals (`0x`, `0b`, `0o`/`0q`) now set a `radix` display
299
+ hint on the generated schema — the paired `ng-form-foundry` release renders
300
+ those fields as hex/octal/binary editors, and the YAML transformer does the
301
+ same for its hex and octal literals.
302
+
297
303
  `0.4.1` — libconfig hardening after a conformance battle test against the C
298
304
  library's own scanner, test corpus, and real srsRAN/OAI configs: `0q`/`0Q`
299
305
  octal, verbatim radix-prefix preservation on edits, sign accepted on decimal
@@ -32,6 +32,13 @@ export interface Leaf {
32
32
  min?: number;
33
33
  max?: number;
34
34
  multipleOf?: number;
35
+ /**
36
+ * Present the value in this base (16 hex, 8 octal, 2 binary) instead of
37
+ * decimal — set when the source document wrote the literal that way. Purely
38
+ * a display hint: the value itself stays a plain number (or, on a string
39
+ * leaf, the exact decimal-digit carry of an integer beyond ±2^53).
40
+ */
41
+ radix?: 2 | 8 | 16;
35
42
  /** The value may be `null` (JSON Schema `type: [T, 'null']`). */
36
43
  nullable?: boolean;
37
44
  /** Optional scalar whose presence is itself data (on/off toggle). */
@@ -47,6 +54,8 @@ export interface LeafList {
47
54
  label?: string;
48
55
  minItems?: number;
49
56
  maxItems?: number;
57
+ /** Present every item in this base — see {@link Leaf.radix}. */
58
+ radix?: 2 | 8 | 16;
50
59
  }
51
60
  export interface NodeGroup {
52
61
  kind: 'nodeGroup';
package/dist/index.d.ts CHANGED
@@ -13,8 +13,8 @@
13
13
  * - `json` — JSON config → form → JSON ({@link jsonTransformer}); same builders
14
14
  * as `yaml`, indent preserved.
15
15
  * - `libconfig` — libconfig document (srsRAN/OAI-style `.cfg`/`.conf`) → form →
16
- * libconfig ({@link libconfigTransformer}); **BETA**, warns once on
17
- * first use; comment- and type-preserving span splicing on revert.
16
+ * libconfig ({@link libconfigTransformer}); comment- and
17
+ * type-preserving span splicing on revert.
18
18
  *
19
19
  * Look transformers up at runtime through {@link TransformerRegistry}, or import
20
20
  * the one you need directly.
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@
14
14
  * - `json` — JSON config → form → JSON ({@link jsonTransformer}); same builders
15
15
  * as `yaml`, indent preserved.
16
16
  * - `libconfig` — libconfig document (srsRAN/OAI-style `.cfg`/`.conf`) → form →
17
- * libconfig ({@link libconfigTransformer}); **BETA**, warns once on
18
- * first use; comment- and type-preserving span splicing on revert.
17
+ * libconfig ({@link libconfigTransformer}); comment- and
18
+ * type-preserving span splicing on revert.
19
19
  *
20
20
  * Look transformers up at runtime through {@link TransformerRegistry}, or import
21
21
  * the one you need directly.
@@ -41,4 +41,4 @@ __exportStar(require("./core"), exports);
41
41
  __exportStar(require("./transformers/yang"), exports);
42
42
  __exportStar(require("./transformers/yaml"), exports);
43
43
  __exportStar(require("./transformers/json"), exports);
44
- __exportStar(require("./transformers/libconfig"), exports); // BETA — warns once on first use
44
+ __exportStar(require("./transformers/libconfig"), exports);
@@ -1,2 +1,2 @@
1
- export { libconfigTransformer, resetLibconfigBetaWarning, type LibconfigBinding, type LibconfigOptions, } from './libconfig-transformer';
1
+ export { libconfigTransformer, type LibconfigBinding, type LibconfigOptions, } from './libconfig-transformer';
2
2
  export { parseLibconfig, LibconfigParseError } from './parser';
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LibconfigParseError = exports.parseLibconfig = exports.resetLibconfigBetaWarning = exports.libconfigTransformer = void 0;
3
+ exports.LibconfigParseError = exports.parseLibconfig = exports.libconfigTransformer = void 0;
4
4
  var libconfig_transformer_1 = require("./libconfig-transformer");
5
5
  Object.defineProperty(exports, "libconfigTransformer", { enumerable: true, get: function () { return libconfig_transformer_1.libconfigTransformer; } });
6
- Object.defineProperty(exports, "resetLibconfigBetaWarning", { enumerable: true, get: function () { return libconfig_transformer_1.resetLibconfigBetaWarning; } });
7
6
  var parser_1 = require("./parser");
8
7
  Object.defineProperty(exports, "parseLibconfig", { enumerable: true, get: function () { return parser_1.parseLibconfig; } });
9
8
  Object.defineProperty(exports, "LibconfigParseError", { enumerable: true, get: function () { return parser_1.LibconfigParseError; } });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A libconfig {@link Transformer} (BETA): turn a libconfig document
2
+ * A libconfig {@link Transformer}: turn a libconfig document
3
3
  * (srsRAN/OAI-style `.cfg`/`.conf`) into a form and write the edited value
4
4
  * back with comments, formatting, and — critically — scalar *types*
5
5
  * preserved (libconfig is statically typed; see {@link import('./revert')}).
@@ -47,8 +47,6 @@ export interface LibconfigBinding {
47
47
  source: string;
48
48
  root: CfgGroup;
49
49
  }
50
- /** Test seam: makes the one-time beta warning observable per test. */
51
- export declare function resetLibconfigBetaWarning(): void;
52
50
  export declare const libconfigTransformer: {
53
51
  id: string;
54
52
  toSchema(source: string, options?: LibconfigOptions): TransformResult<LibconfigBinding>;
@@ -1,28 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.libconfigTransformer = void 0;
4
- exports.resetLibconfigBetaWarning = resetLibconfigBetaWarning;
5
4
  const thesaurus_1 = require("../../core/thesaurus");
6
5
  const json_schema_1 = require("../../core/json-schema");
7
6
  const parser_1 = require("./parser");
8
7
  const schema_1 = require("./schema");
9
8
  const revert_1 = require("./revert");
10
- let warnedBeta = false;
11
- function warnBeta() {
12
- if (warnedBeta)
13
- return;
14
- warnedBeta = true;
15
- console.warn('[ng-form-foundry-transformers] The libconfig transformer is a BETA feature. ' +
16
- 'Verify every write-back (diff toSource output against the original file) before deploying it.');
17
- }
18
- /** Test seam: makes the one-time beta warning observable per test. */
19
- function resetLibconfigBetaWarning() {
20
- warnedBeta = false;
21
- }
22
9
  exports.libconfigTransformer = {
23
10
  id: 'libconfig',
24
11
  toSchema(source, options) {
25
- warnBeta();
26
12
  const root = (0, parser_1.parseLibconfig)(source, { includes: options?.includes });
27
13
  const schema = options?.schema
28
14
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
@@ -32,7 +18,6 @@ exports.libconfigTransformer = {
32
18
  return { schema: labeled, binding: { source, root }, initialValue };
33
19
  },
34
20
  toSource(value, binding) {
35
- warnBeta();
36
21
  return (0, revert_1.applyValueToSource)(binding.source, binding.root, value);
37
22
  },
38
23
  };
@@ -80,14 +80,21 @@ function scalarLeaf(name, scalar) {
80
80
  case 'float':
81
81
  return { kind: 'leaf', name, type: 'number' };
82
82
  case 'int':
83
- return { kind: 'leaf', name, type: 'number', integer: true };
83
+ return withRadix({ kind: 'leaf', name, type: 'number', integer: true }, scalar);
84
84
  case 'int64':
85
85
  // Beyond 2^53 the value rides as an exact decimal string.
86
86
  return typeof scalar.value === 'string'
87
- ? { kind: 'leaf', name, type: 'string', pattern: INTEGER_STRING_PATTERN }
88
- : { kind: 'leaf', name, type: 'number', integer: true };
87
+ ? withRadix({ kind: 'leaf', name, type: 'string', pattern: INTEGER_STRING_PATTERN }, scalar)
88
+ : withRadix({ kind: 'leaf', name, type: 'number', integer: true }, scalar);
89
89
  }
90
90
  }
91
+ /** Carry a non-decimal literal's base onto the leaf as its display radix. */
92
+ function withRadix(leaf, scalar) {
93
+ const radix = scalar.int?.radix;
94
+ if (radix && radix !== 10)
95
+ leaf.radix = radix;
96
+ return leaf;
97
+ }
91
98
  /**
92
99
  * A homogeneous scalar collection → leafList. One int64 element beyond 2^53
93
100
  * degrades the whole list to string carry: a leafList holds one scalar type,
@@ -97,10 +104,23 @@ function listLeaf(name, elements) {
97
104
  if (elements.length === 0)
98
105
  return rawLeaf(name, 'empty collection');
99
106
  const f = (0, parser_1.family)(elements[0].type);
107
+ const radix = f === 'integer' ? sharedRadix(elements) : undefined;
100
108
  if (f === 'integer' && elements.some((e) => typeof e.value === 'string')) {
101
- return { kind: 'leafList', name, type: 'string' };
109
+ return { kind: 'leafList', name, type: 'string', ...(radix && { radix }) };
102
110
  }
103
- return { kind: 'leafList', name, type: f === 'integer' || f === 'float' ? 'number' : f === 'bool' ? 'boolean' : 'string' };
111
+ return {
112
+ kind: 'leafList',
113
+ name,
114
+ type: f === 'integer' || f === 'float' ? 'number' : f === 'bool' ? 'boolean' : 'string',
115
+ ...(radix && { radix }),
116
+ };
117
+ }
118
+ /** The display radix shared by every element, when uniform and non-decimal. */
119
+ function sharedRadix(elements) {
120
+ const first = elements[0]?.int?.radix ?? 10;
121
+ if (first === 10)
122
+ return undefined;
123
+ return elements.every((e) => (e.int?.radix ?? 10) === first) ? first : undefined;
104
124
  }
105
125
  function arrayValue(elements) {
106
126
  const stringCarry = elements.length > 0 &&
@@ -30,6 +30,8 @@ exports.yamlTransformer = {
30
30
  const schema = options?.schema
31
31
  ? (0, json_schema_1.jsonSchemaToNodeGroup)(options.schema, options.rootName, options.schemaOptions)
32
32
  : (0, infer_1.inferNodeGroup)(data, options?.rootName);
33
+ if (!options?.schema && doc.contents)
34
+ annotateRadix(doc.contents, schema);
33
35
  const labeled = options?.thesaurus ? (0, thesaurus_1.applyThesaurus)(schema, options.thesaurus) : schema;
34
36
  return { schema: labeled, binding: doc, initialValue: data };
35
37
  },
@@ -41,6 +43,67 @@ exports.yamlTransformer = {
41
43
  // `satisfies` verifies conformance to the catalog contract while keeping the
42
44
  // concrete sync return types, so direct callers need no `await`.
43
45
  };
46
+ const RADIX_BY_FORMAT = { BIN: 2, OCT: 8, HEX: 16 };
47
+ /**
48
+ * Copy non-decimal integer presentation (`0x`/`0o`/`0b` literals) from the
49
+ * parsed document onto the inferred schema as leaf/leafList `radix` display
50
+ * hints — the plain-data inference cannot see them, because `toJS()` drops the
51
+ * scalar format. The revert needs nothing: scalars are mutated in place, so an
52
+ * edited value re-emits in its literal's own base. Across group-list items the
53
+ * first non-decimal occurrence wins — one shared item schema cannot vary the
54
+ * display per item. Schema-driven mode is untouched (JSON Schema has no radix
55
+ * vocabulary).
56
+ */
57
+ function annotateRadix(node, schema) {
58
+ switch (schema.kind) {
59
+ case 'leaf': {
60
+ const radix = scalarRadix(node);
61
+ if (radix && !schema.radix)
62
+ schema.radix = radix;
63
+ return;
64
+ }
65
+ case 'leafList': {
66
+ if (!(0, yaml_1.isSeq)(node) || schema.radix)
67
+ return;
68
+ const radixes = node.items.map(scalarRadix);
69
+ if (radixes[0] && radixes.every((r) => r === radixes[0]))
70
+ schema.radix = radixes[0];
71
+ return;
72
+ }
73
+ case 'nodeGroup': {
74
+ if (!(0, yaml_1.isMap)(node))
75
+ return;
76
+ for (const pair of node.items) {
77
+ const child = schema.children[pairKey(pair.key)];
78
+ if (child)
79
+ annotateRadix(pair.value, child);
80
+ }
81
+ return;
82
+ }
83
+ case 'nodeGroupList': {
84
+ if (!(0, yaml_1.isSeq)(node))
85
+ return;
86
+ for (const item of node.items)
87
+ annotateRadix(item, schema.type);
88
+ return;
89
+ }
90
+ default:
91
+ return; // choice/map never come out of plain-data inference
92
+ }
93
+ }
94
+ /** The display radix of a non-decimal integer scalar node, else undefined. */
95
+ function scalarRadix(node) {
96
+ if (!(0, yaml_1.isScalar)(node))
97
+ return undefined;
98
+ if (typeof node.value !== 'number' && typeof node.value !== 'bigint')
99
+ return undefined;
100
+ return RADIX_BY_FORMAT[node.format ?? ''];
101
+ }
102
+ /** The string a key node takes as a JS object key, matching `doc.toJS()`. */
103
+ function pairKey(key) {
104
+ const v = (0, yaml_1.isScalar)(key) ? key.value : key;
105
+ return v == null ? '' : String(v);
106
+ }
44
107
  /**
45
108
  * Replace every BigInt from an `intAsBigInt` parse with a form-value scalar: a
46
109
  * plain `number` when it fits the safe range, otherwise its decimal string (so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-form-foundry-transformers",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
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",