j-perm 0.1.2__tar.gz → 0.1.3.1__tar.gz

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.
Files changed (34) hide show
  1. j_perm-0.1.3.1/PKG-INFO +766 -0
  2. j_perm-0.1.3.1/README.md +753 -0
  3. {j_perm-0.1.2 → j_perm-0.1.3.1}/pyproject.toml +1 -1
  4. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/distinct.py +10 -2
  5. j_perm-0.1.3.1/src/j_perm.egg-info/PKG-INFO +766 -0
  6. j_perm-0.1.2/PKG-INFO +0 -116
  7. j_perm-0.1.2/README.md +0 -103
  8. j_perm-0.1.2/src/j_perm.egg-info/PKG-INFO +0 -116
  9. {j_perm-0.1.2 → j_perm-0.1.3.1}/setup.cfg +0 -0
  10. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/__init__.py +0 -0
  11. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/engine.py +0 -0
  12. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/jmes_ext.py +0 -0
  13. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/__init__.py +0 -0
  14. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/_exec.py +0 -0
  15. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/_if.py +0 -0
  16. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/assert.py +0 -0
  17. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/copy.py +0 -0
  18. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/copy_d.py +0 -0
  19. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/delete.py +0 -0
  20. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/foreach.py +0 -0
  21. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/replace_root.py +0 -0
  22. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/set.py +0 -0
  23. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/ops/update.py +0 -0
  24. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/registry.py +0 -0
  25. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/schema/__init__.py +0 -0
  26. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/utils/__init__.py +0 -0
  27. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/utils/pointers.py +0 -0
  28. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/utils/special.py +0 -0
  29. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/utils/subst.py +0 -0
  30. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm/utils/tuples.py +0 -0
  31. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm.egg-info/SOURCES.txt +0 -0
  32. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm.egg-info/dependency_links.txt +0 -0
  33. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm.egg-info/requires.txt +0 -0
  34. {j_perm-0.1.2 → j_perm-0.1.3.1}/src/j_perm.egg-info/top_level.txt +0 -0
@@ -0,0 +1,766 @@
1
+ Metadata-Version: 2.4
2
+ Name: j-perm
3
+ Version: 0.1.3.1
4
+ Summary: jsom permutation library
5
+ Author-email: Roman <kuschanow@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/kuschanow/j-perm
8
+ Project-URL: Source, https://github.com/kuschanow/j-perm
9
+ Project-URL: Tracker, https://github.com/kuschanow/j-perm/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: jmespath
13
+
14
+ # J-Perm
15
+
16
+ A small, composable JSON-transformation DSL implemented in Python.
17
+
18
+ The library lets you describe transformations as **data** (a list of steps) and then apply them to an input document. It supports JSON Pointer paths, custom JMESPath expressions, interpolation with `${...}` syntax, special reference/evaluation values, and a rich set of built-in operations.
19
+
20
+ ---
21
+
22
+ ## Features
23
+
24
+ * JSON Pointer read/write with support for:
25
+
26
+ * root pointers (`""`, `"/"`, `"."`)
27
+ * relative `..` segments
28
+ * list slices like `/items[1:3]`
29
+ * Interpolation templates:
30
+
31
+ * `${/path/to/node}` — JSON Pointer lookup
32
+ * `${int:/path}` / `${float:/path}` / `${bool:/path}` — simple type casters
33
+ * `${? some.jmespath(expression) }` — JMESPath with custom functions
34
+ * Special values:
35
+
36
+ * `$ref` — reference into the source document
37
+ * `$eval` — nested DSL evaluation with optional `$select`
38
+ * Built-in operations:
39
+
40
+ * `set`, `copy`, `copyD`, `delete`, `assert`
41
+ * `foreach`, `if`, `distinct`
42
+ * `replace_root`, `exec`, `update`
43
+ * Shorthand syntax for concise scripts (`~delete`, `~assert`, `field[]`, pointer assignments)
44
+ * Schema helper: approximate JSON Schema generation for a given DSL script.
45
+
46
+ ---
47
+
48
+ ## Core API
49
+
50
+ ### `apply_actions`
51
+
52
+ ```python
53
+ from j_perm import apply_actions
54
+
55
+ result = apply_actions(actions, dest, source)
56
+ ```
57
+
58
+ **Signature:**
59
+
60
+ ```python
61
+ apply_actions(
62
+ actions: Any,
63
+ *,
64
+ dest: MutableMapping[str, Any] | List[Any],
65
+ source: Mapping[str, Any] | List[Any],
66
+ ) -> Mapping[str, Any]
67
+ ```
68
+
69
+ * **`actions`** — DSL script (list or mapping).
70
+ Internally normalized via `normalize_actions()` and shorthand expansion.
71
+ * **`dest`** — initial destination document to transform (typically a dict or list).
72
+ * **`source`** — source document or context; available to pointers, interpolation, `$ref`, `$eval`, and nested operations.
73
+ * Returns a **deep copy** of the final `dest`.
74
+
75
+ ---
76
+
77
+ ## Basic usage
78
+
79
+ ```python
80
+ from j_perm import apply_actions
81
+
82
+ source = {
83
+ "users": [
84
+ {"name": "Alice", "age": 17},
85
+ {"name": "Bob", "age": 22}
86
+ ]
87
+ }
88
+
89
+ actions = [
90
+ # Start with empty list
91
+ {"op": "replace_root", "value": []},
92
+
93
+ # For each user - build a simplified object
94
+ {
95
+ "op": "foreach",
96
+ "in": "/users",
97
+ "as": "u",
98
+ "do": [
99
+ {
100
+ "op": "set",
101
+ "path": "/-",
102
+ "value": {
103
+ "name": "${/u/name}",
104
+ "is_adult": {
105
+ "$eval": [
106
+ {"op": "replace_root", "value": False},
107
+ {
108
+ "op": "if",
109
+ "cond": "${?`${/u/age}` >= `18`}",
110
+ "then": [{"op": "replace_root", "value": True}]
111
+ }
112
+ ]
113
+ }
114
+ }
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+
120
+ result = apply_actions(actions, dest={}, source=source)
121
+ ```
122
+
123
+ ---
124
+
125
+ # Interpolation & expression system (`${...}`)
126
+
127
+ Interpolation is handled by the substitution utility used throughout operations such as `set`, `copy`, `exec`, `update`, schema building, etc.
128
+
129
+ ### 1. JSON Pointer interpolation
130
+
131
+ * **Form:** `${/path/to/value}`
132
+ * Meaning: resolve `/path/to/value` against the current **source context** (the original `source` plus any loop variables, etc.) and inject the value.
133
+
134
+ Example:
135
+
136
+ ```json
137
+ {
138
+ "op": "set",
139
+ "path": "/user/name",
140
+ "value": "Hello, ${/user/first_name}!"
141
+ }
142
+ ```
143
+
144
+ ### 2. Casters
145
+
146
+ * `${int:/age}` → cast value at `/age` to `int`
147
+ * `${float:/height}` → cast to `float`
148
+ * `${bool:/flag}` → cast to `bool`
149
+
150
+ If the cast fails, an exception is raised.
151
+
152
+ ### 3. JMESPath expressions
153
+
154
+ * **Form:** `${? <expression> }`
155
+ * Evaluated against the **source context**, with access to any custom JMESPath functions wired into the engine.
156
+
157
+ Example:
158
+
159
+ ```json
160
+ {
161
+ "op": "set",
162
+ "path": "/expensiveNames",
163
+ "value": "${? items[?price > `10`].name }"
164
+ }
165
+ ```
166
+
167
+ ### 4. Multiple templates in one string
168
+
169
+ Any string can contain multiple `${...}` segments, which are resolved left-to-right.
170
+
171
+ ---
172
+
173
+ # Special values: `$ref` and `$eval`
174
+
175
+ Special values are resolved by `resolve_special()` before normal interpolation/substitution in operations like `set`, `update`, `exec`, `replace_root`.
176
+
177
+ ### `$ref`
178
+
179
+ `{"$ref": "/path"}` means *“use the value at this path from the source”*.
180
+
181
+ Example:
182
+
183
+ ```json
184
+ {
185
+ "op": "set",
186
+ "path": "/user",
187
+ "value": { "$ref": "/rawUser" }
188
+ }
189
+ ```
190
+
191
+ This behaves similarly to a `copy` from `/rawUser`, but can be nested into larger structures and combined with `$eval` and interpolation.
192
+
193
+ ### `$eval`
194
+
195
+ `{"$eval": [...]}` means *“execute this nested DSL script and use its result here”*.
196
+
197
+ Example:
198
+
199
+ ```json
200
+ {
201
+ "op": "set",
202
+ "path": "/flag",
203
+ "value": {
204
+ "$eval": [
205
+ { "op": "replace_root", "value": false },
206
+ {
207
+ "op": "if",
208
+ "cond": "${? some.expression }",
209
+ "then": [{ "op": "replace_root", "value": true }]
210
+ }
211
+ ]
212
+ }
213
+ }
214
+ ```
215
+
216
+ The nested script has the same `source` context as the outer script, and its final result becomes the value injected at `/flag`.
217
+
218
+ ---
219
+
220
+ # Shorthand syntax (shortcuts)
221
+
222
+ Shorthand is expanded by `normalize_actions()` into explicit operation steps.
223
+
224
+ ### How shorthand works internally
225
+
226
+ * `actions` may be:
227
+
228
+ * a list of steps,
229
+ * a mapping without `"op"` — in that case it’s treated as shorthand and expanded.
230
+
231
+ Expansion is done by `_expand_shorthand()`.
232
+
233
+ ### 1. Delete shorthand: `~delete`
234
+
235
+ ```json
236
+ { "~delete": "/path" }
237
+ ```
238
+
239
+ If value is a list:
240
+
241
+ ```json
242
+ { "~delete": ["/a", "/b"] }
243
+ ```
244
+
245
+ Expands into:
246
+
247
+ ```json
248
+ { "op": "delete", "path": "/a" }
249
+ { "op": "delete", "path": "/b" }
250
+ ```
251
+
252
+ ### 2. Assert shorthand: `~assert`
253
+
254
+ If value is a mapping:
255
+
256
+ ```json
257
+ { "~assert": { "/x": 10, "/y": 20 } }
258
+ ```
259
+
260
+ Expands into:
261
+
262
+ ```json
263
+ { "op": "assert", "path": "/x", "equals": 10 }
264
+ { "op": "assert", "path": "/y", "equals": 20 }
265
+ ```
266
+
267
+ If value is a string or list of strings:
268
+
269
+ ```json
270
+ { "~assert": "/x" }
271
+ # or
272
+ { "~assert": ["/x", "/y"] }
273
+ ```
274
+
275
+ Expands into assertions that only check **existence** at those paths.
276
+
277
+ ### 3. Append shorthand: `field[]`
278
+
279
+ A key ending with `[]` means “append to list at this path”.
280
+
281
+ ```json
282
+ { "items[]": 123 }
283
+ ```
284
+
285
+ Expands into:
286
+
287
+ ```json
288
+ { "op": "set", "path": "/items/-", "value": 123 }
289
+ ```
290
+
291
+ ### 4. Pointer assignment shorthand
292
+
293
+ If a value is a **string that starts with `/`**, it’s treated as a pointer and expanded into a `copy` operation:
294
+
295
+ ```json
296
+ { "name": "/user/fullName" }
297
+ ```
298
+
299
+ Expands into:
300
+
301
+ ```json
302
+ {
303
+ "op": "copy",
304
+ "path": "/name",
305
+ "from": "/user/fullName",
306
+ "ignore_missing": true
307
+ }
308
+ ```
309
+
310
+ (See `_is_pointer_string()` and `_expand_shorthand()` for details. )
311
+
312
+ ---
313
+
314
+ # Built-in operations: signatures & parameters
315
+
316
+ Below are all core operations with:
317
+
318
+ * the **step shape**,
319
+ * **required** vs **optional** parameters,
320
+ * **default values**.
321
+
322
+ All defaults are taken directly from the Python implementations.
323
+
324
+ ---
325
+
326
+ ## `set`
327
+
328
+ Set or append a value at a JSON Pointer path in `dest`.
329
+
330
+ ### Signature
331
+
332
+ ```jsonc
333
+ {
334
+ "op": "set", // required
335
+ "path": "/pointer", // required
336
+ "value": <any>, // required
337
+ "create": true, // optional, default: true
338
+ "extend": true // optional, default: true
339
+ }
340
+ ```
341
+
342
+ ### Parameters
343
+
344
+ * **`path`** (required)
345
+ JSON Pointer path where the value is written.
346
+
347
+ * If the path ends with `"/-"`, the value is **appended** to a list.
348
+ * **`value`** (required)
349
+ Any value, including special `$ref` / `$eval` objects and interpolated strings.
350
+ Resolved by `resolve_special()` and then `substitute()`.
351
+ * **`create`** (optional, default **`true`**)
352
+ If `true`, missing parent containers are created.
353
+ * **`extend`** (optional, default **`true`**)
354
+ If `path` is `"/-"` and `value` is a list:
355
+
356
+ * `extend=true` ⇒ list is extended with elements of `value`
357
+ * `extend=false` ⇒ `value` is appended as a single item (nested list)
358
+
359
+ ---
360
+
361
+ ## `copy`
362
+
363
+ Copy a value from `source` (or extended source context) into `dest`. Internally uses `set`.
364
+
365
+ ### Signature
366
+
367
+ ```jsonc
368
+ {
369
+ "op": "copy", // required
370
+ "from": "/source/pointer", // required
371
+ "path": "/target/pointer", // required
372
+ "create": true, // optional, default: true
373
+ "extend": true, // optional, default: true
374
+ "ignore_missing": false, // optional, default: false
375
+ "default": <any> // optional
376
+ }
377
+ ```
378
+
379
+ ### Parameters
380
+
381
+ * **`from`** (required)
382
+ JSON Pointer into the **source** context (after interpolation).
383
+ * **`path`** (required)
384
+ Destination path; same semantics as in `set`.
385
+ * **`create`** (optional, default **`true`**)
386
+ Passed through to `set` — create missing parents.
387
+ * **`extend`** (optional, default **`true`**)
388
+ Passed through to `set` — controls list extension on appends.
389
+ * **`ignore_missing`** (optional, default **`false`**)
390
+ If `true` and `from` cannot be resolved, the operation is a no-op.
391
+ * **`default`** (optional)
392
+ Used when `from` cannot be resolved and `ignore_missing` is **not** set.
393
+ If provided, `default` is copied into `path` instead of raising.
394
+
395
+ ---
396
+
397
+ ## `copyD`
398
+
399
+ Copy a value from the **current `dest`** (self) into another path in `dest`. Internally uses `set`.
400
+
401
+ This is useful for rearranging or duplicating data that has already been built in `dest`, without going back to the `source`.
402
+
403
+ ### Signature
404
+
405
+ ```jsonc
406
+ {
407
+ "op": "copyD", // required
408
+ "from": "/source/pointer", // required
409
+ "path": "/target/pointer", // required
410
+ "create": true, // optional, default: true
411
+ "ignore_missing": false, // optional, default: false
412
+ "default": <any> // optional
413
+ }
414
+ ```
415
+
416
+ ### Parameters
417
+
418
+ * **`from`** (required)
419
+ JSON Pointer evaluated against **`dest`** (not `source`). The pointer itself is first interpolated with `src` (source context), but resolution uses `dest`.
420
+ * **`path`** (required)
421
+ Destination path in `dest`, same semantics as in `set`.
422
+ * **`create`** (optional, default **`true`**)
423
+ Passed to `set` — whether to create missing parent containers.
424
+ * **`ignore_missing`** (optional, default **`false`**)
425
+ If `true` and the `from` pointer cannot be resolved in `dest`, the operation becomes a no-op.
426
+ * **`default`** (optional)
427
+ Used when `from` cannot be resolved and `ignore_missing` is **not** set.
428
+ If provided, that default is deep-copied into `path` instead of raising.
429
+
430
+ ---
431
+
432
+ ## `delete`
433
+
434
+ Delete a node at a JSON Pointer path in `dest`.
435
+
436
+ ### Signature
437
+
438
+ ```jsonc
439
+ {
440
+ "op": "delete", // required
441
+ "path": "/pointer", // required
442
+ "ignore_missing": true // optional, default: true
443
+ }
444
+ ```
445
+
446
+ ### Parameters
447
+
448
+ * **`path`** (required)
449
+ JSON Pointer to the node to delete.
450
+ Must not end with `"-"`.
451
+ * **`ignore_missing`** (optional, default **`true`**)
452
+ If `false`, a missing path raises an error;
453
+ if `true`, missing path is silently ignored.
454
+
455
+ ---
456
+
457
+ ## `assert`
458
+
459
+ Assert node existence and optional equality in `dest`.
460
+
461
+ ### Signature
462
+
463
+ ```jsonc
464
+ {
465
+ "op": "assert", // required
466
+ "path": "/pointer", // required
467
+ "equals": <any> // optional
468
+ }
469
+ ```
470
+
471
+ ### Parameters
472
+
473
+ * **`path`** (required)
474
+ JSON Pointer to check in `dest`.
475
+ If it does not exist, an `AssertionError` is raised.
476
+ * **`equals`** (optional)
477
+ If provided, the value at `path` is compared with `equals`;
478
+ mismatch raises `AssertionError`.
479
+
480
+ ---
481
+
482
+ ## `foreach`
483
+
484
+ Iterate over an array (or mapping) in the source context and execute nested actions.
485
+
486
+ ### Signature
487
+
488
+ ```jsonc
489
+ {
490
+ "op": "foreach", // required
491
+ "in": "/array/path", // required
492
+ "do": [ ... ], // required
493
+ "as": "item", // optional, default: "item"
494
+ "default": [], // optional, default: []
495
+ "skip_empty": true // optional, default: true
496
+ }
497
+ ```
498
+
499
+ ### Parameters
500
+
501
+ * **`in`** (required)
502
+ JSON Pointer to the array in the source context (after interpolation).
503
+ * **`do`** (required)
504
+ Nested DSL script (list or single op) executed for each element.
505
+ * **`as`** (optional, default **`"item"`**)
506
+ Name of the variable bound to the current element in the extended source context.
507
+ * **`default`** (optional, default **`[]`**)
508
+ Used when the pointer in `"in"` cannot be resolved.
509
+ * **`skip_empty`** (optional, default **`true`**)
510
+ If `true` and the resolved array is empty, the loop is skipped.
511
+
512
+ Additional behavior:
513
+
514
+ * If the resolved object is a **dict**, it’s converted to a list of `(key, value)` pairs.
515
+ * On exception in the body, `dest` is restored from a deep copy snapshot.
516
+
517
+ ---
518
+
519
+ ## `if`
520
+
521
+ Conditionally execute nested actions.
522
+
523
+ ### Signature (path-based condition)
524
+
525
+ ```jsonc
526
+ {
527
+ "op": "if", // required
528
+ "path": "/pointer", // required in this mode
529
+ "equals": <any>, // optional
530
+ "exists": true, // optional
531
+ "then": [ ... ], // optional
532
+ "else": [ ... ], // optional
533
+ "do": [ ... ] // optional (fallback success branch)
534
+ }
535
+ ```
536
+
537
+ ### Signature (expression-based condition)
538
+
539
+ ```jsonc
540
+ {
541
+ "op": "if", // required
542
+ "cond": "${?...}", // required in this mode
543
+ "then": [ ... ], // optional
544
+ "else": [ ... ], // optional
545
+ "do": [ ... ] // optional (fallback success branch)
546
+ }
547
+ ```
548
+
549
+ ### Parameters
550
+
551
+ At least **one** of `path` or `cond` must be supplied:
552
+
553
+ * **`path`** (required in path-mode)
554
+
555
+ * If combined with `equals`, condition is `dest[path] == equals` and path must exist.
556
+ * If combined with `exists` (truthy), condition is “path exists”.
557
+ * Else, condition is `bool(dest[path])` (path must exist).
558
+ * **`equals`** (optional, path-mode only)
559
+ Expected value for equality check.
560
+ * **`exists`** (optional, path-mode only)
561
+ If truthy, check is presence of path only.
562
+ * **`cond`** (required in expression-mode)
563
+ Arbitrary interpolated value or expression; `bool(cond)` is used.
564
+
565
+ Branches:
566
+
567
+ * **`then`** (optional)
568
+ Executed when condition is **true**.
569
+ * **`else`** (optional)
570
+ Executed when condition is **false**.
571
+ * **`do`** (optional)
572
+ If condition is **true** and `"then"` is missing, `"do"` is used as the success branch.
573
+
574
+ If no branch is present for the chosen condition result, `dest` is returned unchanged.
575
+
576
+ Error handling:
577
+
578
+ * Before running branch actions, a deep copy snapshot of `dest` is taken.
579
+ * On exception, `dest` is restored to the snapshot.
580
+
581
+ ---
582
+
583
+ ## `distinct`
584
+
585
+ Remove duplicates in a list at the given path, preserving order.
586
+
587
+ ### Signature
588
+
589
+ ```jsonc
590
+ {
591
+ "op": "distinct", // required
592
+ "path": "/list/path", // required
593
+ "key": "/key/pointer" // optional
594
+ }
595
+ ```
596
+
597
+ ### Parameters
598
+
599
+ * **`path`** (required)
600
+ JSON Pointer to a **list** in `dest`.
601
+ If target is not a list, a `TypeError` is raised.
602
+ * **`key`** (optional)
603
+ JSON Pointer evaluated per item to compute the *deduplication key*.
604
+
605
+ * If provided: uniqueness is based on `jptr_get(item, key_path)`.
606
+ * If omitted: the whole `item` is used as the key.
607
+
608
+ ---
609
+
610
+ ## `replace_root`
611
+
612
+ Replace the whole `dest` root value with a new one.
613
+
614
+ ### Signature
615
+
616
+ ```jsonc
617
+ {
618
+ "op": "replace_root", // required
619
+ "value": <any> // required
620
+ }
621
+ ```
622
+
623
+ ### Parameters
624
+
625
+ * **`value`** (required)
626
+ Value to become the new root.
627
+
628
+ * Special values (`$ref`, `$eval`) are resolved via `resolve_special()`.
629
+ * Strings/lists/dicts are then passed through interpolation `substitute()`.
630
+ * The final value is deep-copied.
631
+
632
+ Result of `replace_root` is the new `dest`.
633
+
634
+ ---
635
+
636
+ ## `exec`
637
+
638
+ Execute a nested DSL script held inline or at a pointer.
639
+
640
+ Exactly one of `from` or `actions` must be provided.
641
+
642
+ ### Signature (script from pointer)
643
+
644
+ ```jsonc
645
+ {
646
+ "op": "exec", // required
647
+ "from": "/script/path", // required in this mode
648
+ "default": <any>, // optional
649
+ "merge": false // optional, default: false
650
+ }
651
+ ```
652
+
653
+ ### Signature (inline script)
654
+
655
+ ```jsonc
656
+ {
657
+ "op": "exec", // required
658
+ "actions": [ ... ], // required in this mode
659
+ "merge": false // optional, default: false
660
+ }
661
+ ```
662
+
663
+ ### Parameters
664
+
665
+ * **`from`** (required in pointer-mode)
666
+ Pointer (possibly interpolated) to the actions in the source context.
667
+ If resolution fails:
668
+
669
+ * and `default` is present ⇒ use `default` as the script (after resolving specials and interpolation if it’s str/list/dict),
670
+ * else ⇒ raise `ValueError`.
671
+ * **`actions`** (required in inline-mode)
672
+ Inline DSL script (list or mapping). Special values and templates are resolved.
673
+ * **`merge`** (optional, default **`false`**)
674
+
675
+ * `merge=false` (default): nested script runs with `dest={}`, result **replaces** current `dest`.
676
+ * `merge=true`: nested script runs **on current dest** and the result is returned (like a sub-call to `apply_actions` on same `dest`).
677
+ * **`default`** (optional, pointer-mode only)
678
+ Fallback script if `from` cannot be resolved.
679
+
680
+ ---
681
+
682
+ ## `update`
683
+
684
+ Update a mapping at the given path using either inline value or source mapping.
685
+
686
+ Exactly one of `from` or `value` must be provided.
687
+
688
+ ### Signature
689
+
690
+ ```jsonc
691
+ {
692
+ "op": "update", // required
693
+ "path": "/target/path", // required
694
+ "from": "/source/path", // required in from-mode
695
+ "value": { ... }, // required in value-mode
696
+ "default": { ... }, // optional (from-mode only)
697
+ "create": true, // optional, default: true
698
+ "deep": false // optional, default: false
699
+ }
700
+ ```
701
+
702
+ ### Parameters
703
+
704
+ * **`path`** (required)
705
+ JSON Pointer path to the **mapping** to update.
706
+ * **`from`** (required in from-mode)
707
+ Pointer to mapping in source; deep-copied.
708
+ If resolution fails:
709
+
710
+ * and `default` is provided ⇒ use `default`,
711
+ * else ⇒ raise.
712
+ * **`value`** (required in value-mode)
713
+ Inline mapping; resolved through `resolve_special` and `substitute` if needed.
714
+ * **`default`** (optional, from-mode only)
715
+ Mapping used when `from` cannot be resolved.
716
+ * **`create`** (optional, default **`true`**)
717
+ If `true`, missing containers at `path` are created (including the leaf if needed).
718
+ * **`deep`** (optional, default **`false`**)
719
+
720
+ * `false`: shallow update via `dict.update()`.
721
+ * `true`: recursive deep-merge of nested mappings; leaves and non-mappings are overwritten via deep copy.
722
+
723
+ Constraints:
724
+
725
+ * The resolved update value **must be a mapping**; otherwise `TypeError`.
726
+ * The target at `path` must be a mutable mapping; otherwise `TypeError`.
727
+
728
+ ---
729
+
730
+ # Schema generation
731
+
732
+ ```python
733
+ from j_perm import build_schema
734
+
735
+ schema = build_schema(script)
736
+ ```
737
+
738
+ `schema` is a JSON-Schema-like structure inferred by scanning the script:
739
+
740
+ * Tracks `replace_root`, `set`, `update`, nested `$eval`, etc.
741
+ * Infers basic JSON types from literal values.
742
+
743
+ ---
744
+
745
+ ## Extending with custom operations
746
+
747
+ ```python
748
+ from j_perm import register_op
749
+
750
+ @register_op("my_op")
751
+ def my_op(step, dest, src):
752
+ # implement your logic
753
+ return dest
754
+ ```
755
+
756
+ Any registered operation can be used in scripts via:
757
+
758
+ ```json
759
+ { "op": "my_op", ... }
760
+ ```
761
+
762
+ ---
763
+
764
+ ## License
765
+
766
+ This package is provided as-is; feel free to adapt it to your project structure.