j-perm 0.2.1.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- j_perm/constructs/ref.py +2 -3
- j_perm/engine.py +4 -0
- j_perm/normalizer.py +1 -0
- j_perm/ops/_assert.py +2 -3
- j_perm/ops/_assert_d.py +2 -3
- j_perm/ops/_exec.py +4 -5
- j_perm/ops/_if.py +4 -5
- j_perm/ops/copy.py +3 -4
- j_perm/ops/copy_d.py +3 -4
- j_perm/ops/delete.py +2 -3
- j_perm/ops/distinct.py +4 -5
- j_perm/ops/foreach.py +2 -3
- j_perm/ops/replace_root.py +1 -1
- j_perm/ops/set.py +4 -5
- j_perm/ops/update.py +4 -5
- j_perm/pointers.py +107 -0
- j_perm/subst.py +61 -29
- {j_perm-0.2.1.1.dist-info → j_perm-0.2.3.dist-info}/METADATA +3 -3
- j_perm-0.2.3.dist-info/RECORD +39 -0
- j_perm/utils/__init__.py +0 -0
- j_perm/utils/pointers.py +0 -108
- j_perm-0.2.1.1.dist-info/RECORD +0 -40
- {j_perm-0.2.1.1.dist-info → j_perm-0.2.3.dist-info}/WHEEL +0 -0
- {j_perm-0.2.1.1.dist-info → j_perm-0.2.3.dist-info}/top_level.txt +0 -0
j_perm/constructs/ref.py
CHANGED
|
@@ -4,17 +4,16 @@ import copy
|
|
|
4
4
|
from typing import Mapping, Any
|
|
5
5
|
|
|
6
6
|
from ..special_resolver import _MISSING, SpecialRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@SpecialRegistry.register("$ref")
|
|
11
10
|
def of_ref(node: Mapping[str, Any], src: Mapping[str, Any], engine: "ActionEngine") -> Any:
|
|
12
11
|
# Expand templates inside "$ref" using configured substitutor
|
|
13
|
-
ptr = engine.substitutor.substitute(node["$ref"], src)
|
|
12
|
+
ptr = engine.substitutor.substitute(node["$ref"], src, engine)
|
|
14
13
|
|
|
15
14
|
dflt = node.get("$default", _MISSING)
|
|
16
15
|
try:
|
|
17
|
-
return copy.deepcopy(maybe_slice(ptr, src))
|
|
16
|
+
return copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
18
17
|
except Exception:
|
|
19
18
|
if dflt is not _MISSING:
|
|
20
19
|
return copy.deepcopy(dflt)
|
j_perm/engine.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Any, List, Mapping, TypeAlias, MutableMapping, Union
|
|
|
6
6
|
|
|
7
7
|
from .normalizer import Normalizer
|
|
8
8
|
from .op_handler import Handlers
|
|
9
|
+
from .pointers import PointerManager
|
|
9
10
|
from .special_resolver import SpecialResolver
|
|
10
11
|
from .subst import TemplateSubstitutor
|
|
11
12
|
|
|
@@ -48,6 +49,9 @@ class ActionEngine:
|
|
|
48
49
|
special: SpecialResolver = field(default_factory=SpecialResolver)
|
|
49
50
|
substitutor: TemplateSubstitutor = field(default_factory=TemplateSubstitutor)
|
|
50
51
|
normalizer: Normalizer = field(default_factory=Normalizer)
|
|
52
|
+
pointer_manager: PointerManager = field(default_factory=PointerManager)
|
|
53
|
+
|
|
54
|
+
max_depth: int = 50
|
|
51
55
|
|
|
52
56
|
def apply_actions(
|
|
53
57
|
self,
|
j_perm/normalizer.py
CHANGED
j_perm/ops/_assert.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("assert")
|
|
@@ -14,10 +13,10 @@ def op_assert(
|
|
|
14
13
|
engine: "ActionEngine",
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Assert node existence and/or value at JSON Pointer path in dest."""
|
|
17
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
16
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
18
17
|
|
|
19
18
|
try:
|
|
20
|
-
current =
|
|
19
|
+
current = engine.pointer_manager.get_pointer(src, path)
|
|
21
20
|
except Exception:
|
|
22
21
|
raise AssertionError(f"'{path}' does not exist in source")
|
|
23
22
|
|
j_perm/ops/_assert_d.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("assertD")
|
|
@@ -14,10 +13,10 @@ def op_assert_d(
|
|
|
14
13
|
engine: "ActionEngine",
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Assert node existence and/or value at JSON Pointer path in dest."""
|
|
17
|
-
path = engine.substitutor.substitute(step["path"], dest)
|
|
16
|
+
path = engine.substitutor.substitute(step["path"], dest, engine)
|
|
18
17
|
|
|
19
18
|
try:
|
|
20
|
-
current =
|
|
19
|
+
current = engine.pointer_manager.get_pointer(dest, path)
|
|
21
20
|
except Exception:
|
|
22
21
|
raise AssertionError(f"'{path}' does not exist in destination")
|
|
23
22
|
|
j_perm/ops/_exec.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import maybe_slice
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("exec")
|
|
@@ -24,20 +23,20 @@ def op_exec(
|
|
|
24
23
|
raise ValueError("exec operation requires either 'from' or 'actions' parameter")
|
|
25
24
|
|
|
26
25
|
if has_from:
|
|
27
|
-
actions_ptr = engine.substitutor.substitute(step["from"], src)
|
|
26
|
+
actions_ptr = engine.substitutor.substitute(step["from"], src, engine)
|
|
28
27
|
try:
|
|
29
|
-
actions = maybe_slice(actions_ptr, src)
|
|
28
|
+
actions = engine.pointer_manager.maybe_slice(actions_ptr, src)
|
|
30
29
|
except Exception:
|
|
31
30
|
if "default" in step:
|
|
32
31
|
actions = engine.special.resolve(step["default"], src, engine)
|
|
33
32
|
if isinstance(actions, (str, list, dict)):
|
|
34
|
-
actions = engine.substitutor.substitute(actions, src)
|
|
33
|
+
actions = engine.substitutor.substitute(actions, src, engine)
|
|
35
34
|
else:
|
|
36
35
|
raise ValueError(f"Cannot find actions at {actions_ptr}")
|
|
37
36
|
else:
|
|
38
37
|
actions = engine.special.resolve(step["actions"], src, engine)
|
|
39
38
|
if isinstance(actions, (str, list, dict)):
|
|
40
|
-
actions = engine.substitutor.substitute(actions, src)
|
|
39
|
+
actions = engine.substitutor.substitute(actions, src, engine)
|
|
41
40
|
|
|
42
41
|
merge = bool(step.get("merge", False))
|
|
43
42
|
|
j_perm/ops/_if.py
CHANGED
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("if")
|
|
@@ -17,22 +16,22 @@ def op_if(
|
|
|
17
16
|
"""Conditionally execute nested actions based on a path or expression."""
|
|
18
17
|
if "path" in step:
|
|
19
18
|
try:
|
|
20
|
-
ptr = engine.substitutor.substitute(step["path"], src)
|
|
21
|
-
current = maybe_slice(ptr, dest)
|
|
19
|
+
ptr = engine.substitutor.substitute(step["path"], src, engine)
|
|
20
|
+
current = engine.pointer_manager.maybe_slice(ptr, dest)
|
|
22
21
|
missing = False
|
|
23
22
|
except Exception:
|
|
24
23
|
current = None
|
|
25
24
|
missing = True
|
|
26
25
|
|
|
27
26
|
if "equals" in step:
|
|
28
|
-
expected = engine.substitutor.substitute(step["equals"], src)
|
|
27
|
+
expected = engine.substitutor.substitute(step["equals"], src, engine)
|
|
29
28
|
cond_val = current == expected and not missing
|
|
30
29
|
elif step.get("exists"):
|
|
31
30
|
cond_val = not missing
|
|
32
31
|
else:
|
|
33
32
|
cond_val = bool(current) and not missing
|
|
34
33
|
else:
|
|
35
|
-
raw_cond = engine.substitutor.substitute(step.get("cond"), src)
|
|
34
|
+
raw_cond = engine.substitutor.substitute(step.get("cond"), src, engine)
|
|
36
35
|
cond_val = bool(raw_cond)
|
|
37
36
|
|
|
38
37
|
branch_key = "then" if cond_val else "else"
|
j_perm/ops/copy.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import MutableMapping, Any, Mapping
|
|
|
5
5
|
|
|
6
6
|
from .set import op_set
|
|
7
7
|
from ..op_handler import OpRegistry
|
|
8
|
-
from ..utils.pointers import maybe_slice
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@OpRegistry.register("copy")
|
|
@@ -16,15 +15,15 @@ def op_copy(
|
|
|
16
15
|
engine: "ActionEngine",
|
|
17
16
|
) -> MutableMapping[str, Any]:
|
|
18
17
|
"""Copy value from source pointer into dest path."""
|
|
19
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
18
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
20
19
|
create = bool(step.get("create", True))
|
|
21
20
|
extend_list = bool(step.get("extend", True))
|
|
22
21
|
|
|
23
|
-
ptr = engine.substitutor.substitute(step["from"], src)
|
|
22
|
+
ptr = engine.substitutor.substitute(step["from"], src, engine)
|
|
24
23
|
ignore = bool(step.get("ignore_missing", False))
|
|
25
24
|
|
|
26
25
|
try:
|
|
27
|
-
value = copy.deepcopy(maybe_slice(ptr, src))
|
|
26
|
+
value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
28
27
|
except Exception:
|
|
29
28
|
if "default" in step:
|
|
30
29
|
value = copy.deepcopy(step["default"])
|
j_perm/ops/copy_d.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import MutableMapping, Any, Mapping
|
|
|
5
5
|
|
|
6
6
|
from .set import op_set
|
|
7
7
|
from ..op_handler import OpRegistry
|
|
8
|
-
from ..utils.pointers import maybe_slice
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@OpRegistry.register("copyD")
|
|
@@ -16,14 +15,14 @@ def op_copy_d(
|
|
|
16
15
|
engine: "ActionEngine",
|
|
17
16
|
) -> MutableMapping[str, Any]:
|
|
18
17
|
"""Copy value from dest (self) into another dest path."""
|
|
19
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
18
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
20
19
|
create = bool(step.get("create", True))
|
|
21
20
|
|
|
22
|
-
ptr = engine.substitutor.substitute(step["from"], dest)
|
|
21
|
+
ptr = engine.substitutor.substitute(step["from"], dest, engine)
|
|
23
22
|
ignore = bool(step.get("ignore_missing", False))
|
|
24
23
|
|
|
25
24
|
try:
|
|
26
|
-
value = copy.deepcopy(maybe_slice(ptr, dest))
|
|
25
|
+
value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, dest))
|
|
27
26
|
except Exception:
|
|
28
27
|
if "default" in step:
|
|
29
28
|
value = copy.deepcopy(step["default"])
|
j_perm/ops/delete.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_ensure_parent
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("delete")
|
|
@@ -14,11 +13,11 @@ def op_delete(
|
|
|
14
13
|
engine: "ActionEngine",
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Delete node at the given JSON Pointer path in dest."""
|
|
17
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
16
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
18
17
|
ignore = bool(step.get("ignore_missing", True))
|
|
19
18
|
|
|
20
19
|
try:
|
|
21
|
-
parent, leaf =
|
|
20
|
+
parent, leaf = engine.pointer_manager.ensure_parent(dest, path, create=False)
|
|
22
21
|
|
|
23
22
|
if leaf == "-":
|
|
24
23
|
raise ValueError("'-' not allowed in delete")
|
j_perm/ops/distinct.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import MutableMapping, Any, Mapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_get
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("distinct")
|
|
@@ -14,20 +13,20 @@ def op_distinct(
|
|
|
14
13
|
engine: "ActionEngine",
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Remove duplicates from a list at the given path, preserving order."""
|
|
17
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
18
|
-
lst =
|
|
16
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
17
|
+
lst = engine.pointer_manager.get_pointer(dest, path)
|
|
19
18
|
|
|
20
19
|
if not isinstance(lst, list):
|
|
21
20
|
raise TypeError(f"{path} is not a list (distinct)")
|
|
22
21
|
|
|
23
22
|
key = step.get("key", None)
|
|
24
|
-
key_path = engine.substitutor.substitute(key, src)
|
|
23
|
+
key_path = engine.substitutor.substitute(key, src, engine)
|
|
25
24
|
|
|
26
25
|
seen = set()
|
|
27
26
|
unique = []
|
|
28
27
|
for item in lst:
|
|
29
28
|
if key is not None:
|
|
30
|
-
filter_item =
|
|
29
|
+
filter_item = engine.pointer_manager.get_pointer(item, key_path)
|
|
31
30
|
else:
|
|
32
31
|
filter_item = item
|
|
33
32
|
|
j_perm/ops/foreach.py
CHANGED
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("foreach")
|
|
@@ -15,13 +14,13 @@ def op_foreach(
|
|
|
15
14
|
engine: "ActionEngine",
|
|
16
15
|
) -> MutableMapping[str, Any]:
|
|
17
16
|
"""Iterate over array in source and execute nested actions for each element."""
|
|
18
|
-
arr_ptr = engine.substitutor.substitute(step["in"], src)
|
|
17
|
+
arr_ptr = engine.substitutor.substitute(step["in"], src, engine)
|
|
19
18
|
|
|
20
19
|
default = copy.deepcopy(step.get("default", []))
|
|
21
20
|
skip_empty = bool(step.get("skip_empty", True))
|
|
22
21
|
|
|
23
22
|
try:
|
|
24
|
-
arr = maybe_slice(arr_ptr, src)
|
|
23
|
+
arr = engine.pointer_manager.maybe_slice(arr_ptr, src)
|
|
25
24
|
except Exception:
|
|
26
25
|
arr = default
|
|
27
26
|
|
j_perm/ops/replace_root.py
CHANGED
|
@@ -10,5 +10,5 @@ def op_replace_root(step, dest, src, engine):
|
|
|
10
10
|
"""Replace the whole dest root value with the resolved special value."""
|
|
11
11
|
value = engine.special.resolve(step["value"], src, engine)
|
|
12
12
|
if isinstance(value, (str, list, dict)):
|
|
13
|
-
value = engine.substitutor.substitute(value, src)
|
|
13
|
+
value = engine.substitutor.substitute(value, src, engine)
|
|
14
14
|
return copy.deepcopy(value)
|
j_perm/ops/set.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Any, Mapping, MutableMapping
|
|
4
4
|
|
|
5
5
|
from ..op_handler import OpRegistry
|
|
6
|
-
from ..utils.pointers import jptr_ensure_parent
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@OpRegistry.register("set")
|
|
@@ -14,20 +13,20 @@ def op_set(
|
|
|
14
13
|
engine: "ActionEngine",
|
|
15
14
|
) -> MutableMapping[str, Any]:
|
|
16
15
|
"""Set or append a value at JSON Pointer path in dest."""
|
|
17
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
16
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
18
17
|
create = bool(step.get("create", True))
|
|
19
18
|
extend_list = bool(step.get("extend", True))
|
|
20
19
|
|
|
21
20
|
value = engine.special.resolve(step["value"], src, engine)
|
|
22
21
|
if isinstance(value, (str, list, Mapping)):
|
|
23
|
-
value = engine.substitutor.substitute(value, src)
|
|
22
|
+
value = engine.substitutor.substitute(value, src, engine)
|
|
24
23
|
|
|
25
|
-
parent, leaf =
|
|
24
|
+
parent, leaf = engine.pointer_manager.ensure_parent(dest, path, create=create)
|
|
26
25
|
|
|
27
26
|
if leaf == "-":
|
|
28
27
|
if not isinstance(parent, list):
|
|
29
28
|
if create:
|
|
30
|
-
grand, last =
|
|
29
|
+
grand, last = engine.pointer_manager.ensure_parent(dest, path.rsplit("/", 1)[0], create=True)
|
|
31
30
|
if not isinstance(grand[last], list):
|
|
32
31
|
if grand[last] == {}:
|
|
33
32
|
grand[last] = []
|
j_perm/ops/update.py
CHANGED
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
from typing import MutableMapping, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from ..op_handler import OpRegistry
|
|
7
|
-
from ..utils.pointers import maybe_slice, jptr_ensure_parent
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@OpRegistry.register("update")
|
|
@@ -15,14 +14,14 @@ def op_update(
|
|
|
15
14
|
engine: "ActionEngine",
|
|
16
15
|
) -> MutableMapping[str, Any]:
|
|
17
16
|
"""Update a mapping at the given path using a mapping from source or inline value."""
|
|
18
|
-
path = engine.substitutor.substitute(step["path"], src)
|
|
17
|
+
path = engine.substitutor.substitute(step["path"], src, engine)
|
|
19
18
|
create = bool(step.get("create", True))
|
|
20
19
|
deep = bool(step.get("deep", False))
|
|
21
20
|
|
|
22
21
|
if "from" in step:
|
|
23
|
-
ptr = engine.substitutor.substitute(step["from"], src)
|
|
22
|
+
ptr = engine.substitutor.substitute(step["from"], src, engine)
|
|
24
23
|
try:
|
|
25
|
-
update_value = copy.deepcopy(maybe_slice(ptr, src))
|
|
24
|
+
update_value = copy.deepcopy(engine.pointer_manager.maybe_slice(ptr, src))
|
|
26
25
|
except Exception:
|
|
27
26
|
if "default" in step:
|
|
28
27
|
update_value = copy.deepcopy(step["default"])
|
|
@@ -31,7 +30,7 @@ def op_update(
|
|
|
31
30
|
elif "value" in step:
|
|
32
31
|
update_value = engine.special.resolve(step["value"], src, engine)
|
|
33
32
|
if isinstance(update_value, (str, list, Mapping)):
|
|
34
|
-
update_value = engine.substitutor.substitute(update_value, src)
|
|
33
|
+
update_value = engine.substitutor.substitute(update_value, src, engine)
|
|
35
34
|
else:
|
|
36
35
|
raise ValueError("update operation requires either 'from' or 'value' parameter")
|
|
37
36
|
|
j_perm/pointers.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Mapping, MutableMapping, List, Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PointerManager:
|
|
8
|
+
_SLICE_RE = re.compile(r"(.+)\[(-?\d*):(-?\d*)]$")
|
|
9
|
+
|
|
10
|
+
def _decode(self, tok: str) -> str:
|
|
11
|
+
"""Decode a single JSON Pointer token, handling RFC6901-style escapes."""
|
|
12
|
+
return (
|
|
13
|
+
tok.replace("~0", "~")
|
|
14
|
+
.replace("~1", "/")
|
|
15
|
+
.replace("~2", "$")
|
|
16
|
+
.replace("~3", ".")
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def get_pointer(self, doc: Any, ptr: str) -> Any:
|
|
20
|
+
"""Read value by JSON Pointer, supporting root and '..' segments."""
|
|
21
|
+
if ptr in ("", "/", "."):
|
|
22
|
+
return doc
|
|
23
|
+
|
|
24
|
+
tokens = ptr.lstrip("/").split("/")
|
|
25
|
+
cur: Any = doc
|
|
26
|
+
parents: List[Tuple[Any, Any]] = []
|
|
27
|
+
|
|
28
|
+
for raw_tok in tokens:
|
|
29
|
+
if raw_tok == "..":
|
|
30
|
+
if parents:
|
|
31
|
+
cur, _ = parents.pop()
|
|
32
|
+
else:
|
|
33
|
+
cur = doc
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
key = self._decode(raw_tok)
|
|
37
|
+
|
|
38
|
+
if isinstance(cur, (list, tuple)):
|
|
39
|
+
idx = int(key)
|
|
40
|
+
parents.append((cur, idx))
|
|
41
|
+
cur = cur[idx]
|
|
42
|
+
else:
|
|
43
|
+
parents.append((cur, key))
|
|
44
|
+
cur = cur[key]
|
|
45
|
+
|
|
46
|
+
return cur
|
|
47
|
+
|
|
48
|
+
def maybe_slice(self, ptr: str, src: Mapping[str, Any]) -> Any:
|
|
49
|
+
"""Resolve a pointer and optional Python-style slice suffix '[start:end]' for arrays."""
|
|
50
|
+
m = self._SLICE_RE.match(ptr)
|
|
51
|
+
if m:
|
|
52
|
+
base, s, e = m.groups()
|
|
53
|
+
seq = self.get_pointer(src, base)
|
|
54
|
+
if not isinstance(seq, (list, tuple)):
|
|
55
|
+
raise TypeError(f"{base} is not a list (slice requested)")
|
|
56
|
+
|
|
57
|
+
start = int(s) if s else None
|
|
58
|
+
end = int(e) if e else None
|
|
59
|
+
return seq[start:end]
|
|
60
|
+
|
|
61
|
+
return self.get_pointer(src, ptr)
|
|
62
|
+
|
|
63
|
+
def ensure_parent(
|
|
64
|
+
self,
|
|
65
|
+
doc: MutableMapping[str, Any],
|
|
66
|
+
ptr: str,
|
|
67
|
+
*,
|
|
68
|
+
create: bool = False,
|
|
69
|
+
):
|
|
70
|
+
"""Return (container, leaf_key) for ptr, optionally creating intermediate nodes."""
|
|
71
|
+
raw_parts = ptr.lstrip("/").split("/")
|
|
72
|
+
parts: List[str] = []
|
|
73
|
+
|
|
74
|
+
for raw in raw_parts:
|
|
75
|
+
if raw == "..":
|
|
76
|
+
if parts:
|
|
77
|
+
parts.pop()
|
|
78
|
+
continue
|
|
79
|
+
parts.append(raw)
|
|
80
|
+
|
|
81
|
+
if not parts:
|
|
82
|
+
return doc, ""
|
|
83
|
+
|
|
84
|
+
cur: Any = doc
|
|
85
|
+
|
|
86
|
+
for raw in parts[:-1]:
|
|
87
|
+
token = self._decode(raw)
|
|
88
|
+
|
|
89
|
+
if isinstance(cur, list):
|
|
90
|
+
idx = int(token)
|
|
91
|
+
if idx >= len(cur):
|
|
92
|
+
if create:
|
|
93
|
+
while idx >= len(cur):
|
|
94
|
+
cur.append({})
|
|
95
|
+
else:
|
|
96
|
+
raise IndexError(f"{ptr}: index {idx} out of range")
|
|
97
|
+
cur = cur[idx]
|
|
98
|
+
else:
|
|
99
|
+
if token not in cur:
|
|
100
|
+
if create:
|
|
101
|
+
cur[token] = {}
|
|
102
|
+
else:
|
|
103
|
+
raise KeyError(f"{ptr}: missing key '{token}'")
|
|
104
|
+
cur = cur[token]
|
|
105
|
+
|
|
106
|
+
leaf = self._decode(parts[-1])
|
|
107
|
+
return cur, leaf
|
j_perm/subst.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import copy
|
|
4
3
|
import json
|
|
5
4
|
from dataclasses import dataclass, field
|
|
6
5
|
from typing import Any, Callable, Mapping
|
|
@@ -8,8 +7,6 @@ from typing import Any, Callable, Mapping
|
|
|
8
7
|
import jmespath
|
|
9
8
|
from jmespath import functions as _jp_funcs
|
|
10
9
|
|
|
11
|
-
from j_perm.utils.pointers import maybe_slice
|
|
12
|
-
|
|
13
10
|
# =============================================================================
|
|
14
11
|
# Types
|
|
15
12
|
# =============================================================================
|
|
@@ -186,21 +183,30 @@ class TemplateSubstitutor:
|
|
|
186
183
|
# Public API
|
|
187
184
|
# -------------------------------------------------------------------------
|
|
188
185
|
|
|
189
|
-
def substitute(self, obj: Any, data: Mapping[str, Any]) -> Any:
|
|
190
|
-
|
|
186
|
+
def substitute(self, obj: Any, data: Mapping[str, Any], engine: "ActionEngine") -> Any:
|
|
187
|
+
res = self.deep_substitute(obj, data, engine)
|
|
188
|
+
return self._final_unescape(res)
|
|
191
189
|
|
|
192
|
-
def flat_substitute(self, tmpl: str, data: Mapping[str, Any]) -> Any:
|
|
193
|
-
if
|
|
190
|
+
def flat_substitute(self, tmpl: str, data: Mapping[str, Any], engine: "ActionEngine") -> Any:
|
|
191
|
+
if not self._has_unescaped_placeholder(tmpl):
|
|
194
192
|
return tmpl
|
|
195
193
|
|
|
196
|
-
if tmpl.startswith("${") and tmpl.endswith("}"):
|
|
197
|
-
body = tmpl[2:-1]
|
|
198
|
-
return copy.deepcopy(self._resolve_expr(body, data))
|
|
199
|
-
|
|
200
194
|
out: list[str] = []
|
|
201
195
|
i = 0
|
|
202
196
|
|
|
203
197
|
while i < len(tmpl):
|
|
198
|
+
# literal "${"
|
|
199
|
+
if tmpl[i:i + 3] == "$${":
|
|
200
|
+
out.append("$${")
|
|
201
|
+
i += 3
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
# literal "$"
|
|
205
|
+
if tmpl[i:i + 2] == "$$":
|
|
206
|
+
out.append("$$")
|
|
207
|
+
i += 2
|
|
208
|
+
continue
|
|
209
|
+
|
|
204
210
|
if tmpl[i: i + 2] == "${":
|
|
205
211
|
depth = 0
|
|
206
212
|
j = i + 2
|
|
@@ -213,7 +219,7 @@ class TemplateSubstitutor:
|
|
|
213
219
|
elif ch == "}":
|
|
214
220
|
if depth == 0:
|
|
215
221
|
expr = tmpl[i + 2: j]
|
|
216
|
-
val = self._resolve_expr(expr, data)
|
|
222
|
+
val = self._resolve_expr(expr, data, engine)
|
|
217
223
|
|
|
218
224
|
if isinstance(val, (Mapping, list)):
|
|
219
225
|
rendered = json.dumps(val, ensure_ascii=False)
|
|
@@ -236,29 +242,29 @@ class TemplateSubstitutor:
|
|
|
236
242
|
|
|
237
243
|
return "".join(out)
|
|
238
244
|
|
|
239
|
-
def deep_substitute(self, obj: Any, data: Mapping[str, Any], _depth: int = 0) -> Any:
|
|
240
|
-
if _depth >
|
|
245
|
+
def deep_substitute(self, obj: Any, data: Mapping[str, Any], engine: "ActionEngine", _depth: int = 0) -> Any:
|
|
246
|
+
if _depth > engine.max_depth:
|
|
241
247
|
raise RecursionError("too deep interpolation")
|
|
242
248
|
|
|
243
249
|
if isinstance(obj, str):
|
|
244
|
-
out = self.flat_substitute(obj, data)
|
|
245
|
-
if isinstance(out, str) and
|
|
246
|
-
return self.deep_substitute(out, data, _depth + 1)
|
|
250
|
+
out = self.flat_substitute(obj, data, engine)
|
|
251
|
+
if isinstance(out, str) and self._has_unescaped_placeholder(out):
|
|
252
|
+
return self.deep_substitute(out, data, engine, _depth + 1)
|
|
247
253
|
return out
|
|
248
254
|
|
|
249
255
|
if isinstance(obj, list):
|
|
250
|
-
return [self.deep_substitute(item, data, _depth) for item in obj]
|
|
256
|
+
return [self.deep_substitute(item, data, engine, _depth) for item in obj]
|
|
251
257
|
|
|
252
258
|
if isinstance(obj, tuple):
|
|
253
|
-
return [self.deep_substitute(item, data, _depth) for item in obj]
|
|
259
|
+
return [self.deep_substitute(item, data, engine, _depth) for item in obj]
|
|
254
260
|
|
|
255
261
|
if isinstance(obj, Mapping):
|
|
256
262
|
out: dict[Any, Any] = {}
|
|
257
263
|
for k, v in obj.items():
|
|
258
|
-
new_key = self.deep_substitute(k, data, _depth) if isinstance(k, str) else k
|
|
264
|
+
new_key = self.deep_substitute(k, data, engine, _depth) if isinstance(k, str) else k
|
|
259
265
|
if new_key in out:
|
|
260
266
|
raise KeyError(f"duplicate key after substitution: {new_key!r}")
|
|
261
|
-
out[new_key] = self.deep_substitute(v, data, _depth)
|
|
267
|
+
out[new_key] = self.deep_substitute(v, data, engine, _depth)
|
|
262
268
|
return out
|
|
263
269
|
|
|
264
270
|
return obj
|
|
@@ -267,7 +273,33 @@ class TemplateSubstitutor:
|
|
|
267
273
|
# Internals
|
|
268
274
|
# -------------------------------------------------------------------------
|
|
269
275
|
|
|
270
|
-
def
|
|
276
|
+
def _final_unescape(self, obj: Any) -> Any:
|
|
277
|
+
if isinstance(obj, str):
|
|
278
|
+
return obj.replace("$${", "${").replace("$$", "$")
|
|
279
|
+
if isinstance(obj, list):
|
|
280
|
+
return [self._final_unescape(x) for x in obj]
|
|
281
|
+
if isinstance(obj, tuple):
|
|
282
|
+
return tuple(self._final_unescape(x) for x in obj)
|
|
283
|
+
if isinstance(obj, Mapping):
|
|
284
|
+
return {self._final_unescape(k) if isinstance(k, str) else k: self._final_unescape(v)
|
|
285
|
+
for k, v in obj.items()}
|
|
286
|
+
return obj
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def _has_unescaped_placeholder(s: str) -> bool:
|
|
290
|
+
i = 0
|
|
291
|
+
while True:
|
|
292
|
+
j = s.find("${", i)
|
|
293
|
+
if j == -1:
|
|
294
|
+
return False
|
|
295
|
+
if j > 0 and s[j - 1] == "$":
|
|
296
|
+
i = j + 2
|
|
297
|
+
continue
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
def _resolve_expr(self, expr: str, data: Mapping[str, Any], engine: "ActionEngine") -> Any:
|
|
271
303
|
expr = expr.strip()
|
|
272
304
|
|
|
273
305
|
# 1) Casters
|
|
@@ -275,26 +307,26 @@ class TemplateSubstitutor:
|
|
|
275
307
|
tag = f"{prefix}:"
|
|
276
308
|
if expr.startswith(tag):
|
|
277
309
|
inner = expr[len(tag):]
|
|
278
|
-
value = self.flat_substitute(inner, data)
|
|
310
|
+
value = self.flat_substitute(inner, data, engine)
|
|
279
311
|
|
|
280
|
-
if isinstance(value, str) and
|
|
281
|
-
value = self.flat_substitute(value, data)
|
|
312
|
+
if isinstance(value, str) and self._has_unescaped_placeholder(value):
|
|
313
|
+
value = self.flat_substitute(value, data, engine)
|
|
282
314
|
|
|
283
315
|
return fn(value)
|
|
284
316
|
|
|
285
317
|
# 2) JMESPath
|
|
286
318
|
if expr.startswith("?"):
|
|
287
319
|
query_raw = expr[1:].lstrip()
|
|
288
|
-
query_expanded = self.flat_substitute(query_raw, data)
|
|
320
|
+
query_expanded = self.flat_substitute(query_raw, data, engine)
|
|
289
321
|
return jmespath.search(query_expanded, data, options=self._jp_options)
|
|
290
322
|
|
|
291
323
|
# 3) Nested template
|
|
292
|
-
if
|
|
293
|
-
return self.flat_substitute(expr, data)
|
|
324
|
+
if self._has_unescaped_placeholder(expr):
|
|
325
|
+
return self.flat_substitute(expr, data, engine)
|
|
294
326
|
|
|
295
327
|
# 4) JSON Pointer fallback
|
|
296
328
|
pointer = "/" + expr.lstrip("/")
|
|
297
329
|
try:
|
|
298
|
-
return maybe_slice(pointer, data)
|
|
330
|
+
return engine.pointer_manager.maybe_slice(pointer, data)
|
|
299
331
|
except Exception:
|
|
300
332
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: j-perm
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: json permutation library
|
|
5
5
|
Author-email: Roman <kuschanow@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -799,8 +799,8 @@ def my_op(step, dest, src, engine):
|
|
|
799
799
|
from j_perm import SpecialRegistry
|
|
800
800
|
|
|
801
801
|
@SpecialRegistry.register("$upper")
|
|
802
|
-
def sp_upper(node, src, resolver):
|
|
803
|
-
value = resolver.substitutor.substitute(node["$upper"], src)
|
|
802
|
+
def sp_upper(node, src, resolver, engine):
|
|
803
|
+
value = resolver.substitutor.substitute(node["$upper"], src, engine)
|
|
804
804
|
return str(value).upper()
|
|
805
805
|
```
|
|
806
806
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
j_perm/__init__.py,sha256=81aLdcmlUg51R6xR-VXVDDUeM87ezy3ZuiLpblHZ_wk,873
|
|
2
|
+
j_perm/engine.py,sha256=ETrLMr2VecSpiT8Ai9AL_-TW6sAlSQ-1Jpf7lttHNPY,4061
|
|
3
|
+
j_perm/normalizer.py,sha256=zxWtARRNuzS-zgFuB40dqU0oaUUk8PLKJNOPNb5wbRw,2672
|
|
4
|
+
j_perm/op_handler.py,sha256=h28thz_18DwZTb9IZtTd6kfsZj7hN4UsmU4uf3aMYpo,1649
|
|
5
|
+
j_perm/pointers.py,sha256=Czhq9vre_HnxhT1iOs8cDcKdCvEum5_YdvaEzoZ3iTY,3205
|
|
6
|
+
j_perm/special_resolver.py,sha256=khRVWXrE3Vch1gbxsfFwj9JQ37pZ-_ojMeh-G2Vv7bs,4032
|
|
7
|
+
j_perm/subst.py,sha256=I1O9qdmJp9TYjpfnqOgyIgL064hWGCmsGoxCbFWCBL4,11026
|
|
8
|
+
j_perm/casters/__init__.py,sha256=mD49oMEyj6oDfSKXEJ8DbnmqkSypfEEg3VMI_rfaK-4,114
|
|
9
|
+
j_perm/casters/_bool.py,sha256=qRsT7IcFBApD09elKUyUUaZmfePSIsOsKQHQHsgnHsw,230
|
|
10
|
+
j_perm/casters/_float.py,sha256=C4r-h6zbkjmQnoEdE2RDMhSkW9oGrvxbQngJZ2T7_tU,153
|
|
11
|
+
j_perm/casters/_int.py,sha256=f1BKqsULiG6Puov1qcGS_inSnv0hZtQ0deQEMP0v0i4,145
|
|
12
|
+
j_perm/casters/_str.py,sha256=v1_BD1_hPbOtvO5CKYAlNEGbMhpR56eKCnV2a2Q4kGU,145
|
|
13
|
+
j_perm/constructs/__init__.py,sha256=TTUPSMyvj8HSfPikrFqXaFsechc9aOIbjtVjy4TOolE,50
|
|
14
|
+
j_perm/constructs/eval.py,sha256=UzjZK27UAzsl3s26Y3RWBPhuKX1Az0fK6xk-cgpS8pQ,452
|
|
15
|
+
j_perm/constructs/ref.py,sha256=WgzEo54IhGWvBVSwPGTeqCRLDyK5uPJ01p5Wjd1YdNM,634
|
|
16
|
+
j_perm/funcs/__init__.py,sha256=8yIafxhCH5B7jXrsautQEtl4Qt8RcUK7IWNniORj9ow,31
|
|
17
|
+
j_perm/funcs/subtract.py,sha256=8W49TsrnIU4EVjS8tWQY2GXlX2nAaQ1O9O1JlrVYdIw,288
|
|
18
|
+
j_perm/ops/__init__.py,sha256=j_U4Aq0iwEvZOF0jqMmlQA0NisJsHNJWzqkfyMWuHFA,364
|
|
19
|
+
j_perm/ops/_assert.py,sha256=vXsQBNIBQYLNSrO6u-vg4_oavvK5N_u-MAc8AqnISBk,769
|
|
20
|
+
j_perm/ops/_assert_d.py,sha256=sG1DC-ElJi54EN2LPzzbvw_5jtOoR1w6j_Ml3mO5Gwo,779
|
|
21
|
+
j_perm/ops/_exec.py,sha256=e3TCFN7bzJ_mjPbpm4ZZq0Dbz-YoJhOiHH1Dt4PwY3k,1919
|
|
22
|
+
j_perm/ops/_if.py,sha256=QziuiEOSkcOMMbyHm_C9IYRhybdtqiLdtQSBbyEL6To,1596
|
|
23
|
+
j_perm/ops/copy.py,sha256=XA5EysIAbbX7hrS1aeN7MidiX_8YyaeTjsZCKoMYbT0,1108
|
|
24
|
+
j_perm/ops/copy_d.py,sha256=iTlZ6lE1gMMcCaUjVWhbXHpBxd97OLXM9Ml5SP0Xvbw,1015
|
|
25
|
+
j_perm/ops/delete.py,sha256=YwPyMxPZ2LnCVC__bivvDLxlsvT2mnrmNIH7Qgw7bjk,894
|
|
26
|
+
j_perm/ops/distinct.py,sha256=eL7Ja4YNVCpsrgiuMxew2xqO3pLitJyAI3ygzy3hSVs,1066
|
|
27
|
+
j_perm/ops/foreach.py,sha256=zbELdguV5He_7nBoekaFsn6dsqBQuRzCoPIB44weUbE,1415
|
|
28
|
+
j_perm/ops/replace_root.py,sha256=klPY9E3KsPfnswIM4CdX_JPXGE1WWjr0sXSXSwn3ooo,453
|
|
29
|
+
j_perm/ops/set.py,sha256=YJjS1BXEEdPxFbUcJGu4YZPt0HHsk9cixpk0OnVQB4E,1886
|
|
30
|
+
j_perm/ops/update.py,sha256=9ouXm4ICNBqr03gJXUvV78AiytgkA4K2Fwb648r8rhE,2457
|
|
31
|
+
j_perm/schema/__init__.py,sha256=mhEbGdi1MgLeEm1IL8-tkTtbIdrdlvQi22sBr7vCUqg,3392
|
|
32
|
+
j_perm/shorthands/__init__.py,sha256=zvsu15iExYtUrct6Ob4IRs6Ao46PoZaCKVIUhMBY4Dk,117
|
|
33
|
+
j_perm/shorthands/_assert.py,sha256=iRvuUGlN5Bj9G5Bo-aXUa4MQujgmrR3N-PhDeee6xcY,555
|
|
34
|
+
j_perm/shorthands/assign_or_append.py,sha256=TsSzaQXb5qoKtY6tvx_skHob-695Zii5roUfXltznAQ,621
|
|
35
|
+
j_perm/shorthands/delete.py,sha256=PZeTRcP6-Q3SdbdyruWWXtMJ9mTnz1CBCHiOpHNI06Q,311
|
|
36
|
+
j_perm-0.2.3.dist-info/METADATA,sha256=VC5n1B4bYvfhZ2Yl6m-MPlVknNUZESOjt9z4R9MgkkU,17674
|
|
37
|
+
j_perm-0.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
38
|
+
j_perm-0.2.3.dist-info/top_level.txt,sha256=yveBxREqVn9DliNwmen1QWoxR0uta7bU5giS1CCffRI,7
|
|
39
|
+
j_perm-0.2.3.dist-info/RECORD,,
|
j_perm/utils/__init__.py
DELETED
|
File without changes
|
j_perm/utils/pointers.py
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import Any, Mapping, MutableMapping, List, Tuple
|
|
5
|
-
|
|
6
|
-
_SLICE_RE = re.compile(r"(.+)\[(-?\d*):(-?\d*)]$")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _decode(tok: str) -> str:
|
|
10
|
-
"""Decode a single JSON Pointer token, handling RFC6901-style escapes."""
|
|
11
|
-
return (
|
|
12
|
-
tok.replace("~0", "~")
|
|
13
|
-
.replace("~1", "/")
|
|
14
|
-
.replace("~2", "$")
|
|
15
|
-
.replace("~3", ".")
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def jptr_get(doc: Any, ptr: str) -> Any:
|
|
20
|
-
"""Read value by JSON Pointer, supporting root and '..' segments."""
|
|
21
|
-
if ptr in ("", "/", "."):
|
|
22
|
-
return doc
|
|
23
|
-
|
|
24
|
-
tokens = ptr.lstrip("/").split("/")
|
|
25
|
-
cur: Any = doc
|
|
26
|
-
parents: List[Tuple[Any, Any]] = []
|
|
27
|
-
|
|
28
|
-
for raw_tok in tokens:
|
|
29
|
-
if raw_tok == "..":
|
|
30
|
-
if parents:
|
|
31
|
-
cur, _ = parents.pop()
|
|
32
|
-
else:
|
|
33
|
-
cur = doc
|
|
34
|
-
continue
|
|
35
|
-
|
|
36
|
-
key = _decode(raw_tok)
|
|
37
|
-
|
|
38
|
-
if isinstance(cur, (list, tuple)):
|
|
39
|
-
idx = int(key)
|
|
40
|
-
parents.append((cur, idx))
|
|
41
|
-
cur = cur[idx]
|
|
42
|
-
else:
|
|
43
|
-
parents.append((cur, key))
|
|
44
|
-
cur = cur[key]
|
|
45
|
-
|
|
46
|
-
return cur
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def maybe_slice(ptr: str, src: Mapping[str, Any]) -> Any:
|
|
50
|
-
"""Resolve a pointer and optional Python-style slice suffix '[start:end]' for arrays."""
|
|
51
|
-
m = _SLICE_RE.match(ptr)
|
|
52
|
-
if m:
|
|
53
|
-
base, s, e = m.groups()
|
|
54
|
-
seq = jptr_get(src, base)
|
|
55
|
-
if not isinstance(seq, (list, tuple)):
|
|
56
|
-
raise TypeError(f"{base} is not a list (slice requested)")
|
|
57
|
-
|
|
58
|
-
start = int(s) if s else None
|
|
59
|
-
end = int(e) if e else None
|
|
60
|
-
return seq[start:end]
|
|
61
|
-
|
|
62
|
-
return jptr_get(src, ptr)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def jptr_ensure_parent(
|
|
66
|
-
doc: MutableMapping[str, Any],
|
|
67
|
-
ptr: str,
|
|
68
|
-
*,
|
|
69
|
-
create: bool = False,
|
|
70
|
-
):
|
|
71
|
-
"""Return (container, leaf_key) for ptr, optionally creating intermediate nodes."""
|
|
72
|
-
raw_parts = ptr.lstrip("/").split("/")
|
|
73
|
-
parts: List[str] = []
|
|
74
|
-
|
|
75
|
-
for raw in raw_parts:
|
|
76
|
-
if raw == "..":
|
|
77
|
-
if parts:
|
|
78
|
-
parts.pop()
|
|
79
|
-
continue
|
|
80
|
-
parts.append(raw)
|
|
81
|
-
|
|
82
|
-
if not parts:
|
|
83
|
-
return doc, ""
|
|
84
|
-
|
|
85
|
-
cur: Any = doc
|
|
86
|
-
|
|
87
|
-
for raw in parts[:-1]:
|
|
88
|
-
token = _decode(raw)
|
|
89
|
-
|
|
90
|
-
if isinstance(cur, list):
|
|
91
|
-
idx = int(token)
|
|
92
|
-
if idx >= len(cur):
|
|
93
|
-
if create:
|
|
94
|
-
while idx >= len(cur):
|
|
95
|
-
cur.append({})
|
|
96
|
-
else:
|
|
97
|
-
raise IndexError(f"{ptr}: index {idx} out of range")
|
|
98
|
-
cur = cur[idx]
|
|
99
|
-
else:
|
|
100
|
-
if token not in cur:
|
|
101
|
-
if create:
|
|
102
|
-
cur[token] = {}
|
|
103
|
-
else:
|
|
104
|
-
raise KeyError(f"{ptr}: missing key '{token}'")
|
|
105
|
-
cur = cur[token]
|
|
106
|
-
|
|
107
|
-
leaf = _decode(parts[-1])
|
|
108
|
-
return cur, leaf
|
j_perm-0.2.1.1.dist-info/RECORD
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
j_perm/__init__.py,sha256=81aLdcmlUg51R6xR-VXVDDUeM87ezy3ZuiLpblHZ_wk,873
|
|
2
|
-
j_perm/engine.py,sha256=iaUxB7EWcqvWsP4Z8z49a6-atX9br1tTC-WlZVrVo-A,3923
|
|
3
|
-
j_perm/normalizer.py,sha256=50pff5j3sTrCu2Fp_SW6m99qwt88CladpYlWpQ3c-ac,2671
|
|
4
|
-
j_perm/op_handler.py,sha256=h28thz_18DwZTb9IZtTd6kfsZj7hN4UsmU4uf3aMYpo,1649
|
|
5
|
-
j_perm/special_resolver.py,sha256=khRVWXrE3Vch1gbxsfFwj9JQ37pZ-_ojMeh-G2Vv7bs,4032
|
|
6
|
-
j_perm/subst.py,sha256=oW21Gs2nTEQLcOC_xMKzeuZFtfNvnB2QZAIWl7U4TSg,9802
|
|
7
|
-
j_perm/casters/__init__.py,sha256=mD49oMEyj6oDfSKXEJ8DbnmqkSypfEEg3VMI_rfaK-4,114
|
|
8
|
-
j_perm/casters/_bool.py,sha256=qRsT7IcFBApD09elKUyUUaZmfePSIsOsKQHQHsgnHsw,230
|
|
9
|
-
j_perm/casters/_float.py,sha256=C4r-h6zbkjmQnoEdE2RDMhSkW9oGrvxbQngJZ2T7_tU,153
|
|
10
|
-
j_perm/casters/_int.py,sha256=f1BKqsULiG6Puov1qcGS_inSnv0hZtQ0deQEMP0v0i4,145
|
|
11
|
-
j_perm/casters/_str.py,sha256=v1_BD1_hPbOtvO5CKYAlNEGbMhpR56eKCnV2a2Q4kGU,145
|
|
12
|
-
j_perm/constructs/__init__.py,sha256=TTUPSMyvj8HSfPikrFqXaFsechc9aOIbjtVjy4TOolE,50
|
|
13
|
-
j_perm/constructs/eval.py,sha256=UzjZK27UAzsl3s26Y3RWBPhuKX1Az0fK6xk-cgpS8pQ,452
|
|
14
|
-
j_perm/constructs/ref.py,sha256=GtR2hpENev9k1lsx3mHGM8956vBfrPUgHm3IydYr-_I,644
|
|
15
|
-
j_perm/funcs/__init__.py,sha256=8yIafxhCH5B7jXrsautQEtl4Qt8RcUK7IWNniORj9ow,31
|
|
16
|
-
j_perm/funcs/subtract.py,sha256=8W49TsrnIU4EVjS8tWQY2GXlX2nAaQ1O9O1JlrVYdIw,288
|
|
17
|
-
j_perm/ops/__init__.py,sha256=j_U4Aq0iwEvZOF0jqMmlQA0NisJsHNJWzqkfyMWuHFA,364
|
|
18
|
-
j_perm/ops/_assert.py,sha256=O7MFxW3FDCglPMx-tBwi67k2Wd-Mds996AQdeGLbM6Q,773
|
|
19
|
-
j_perm/ops/_assert_d.py,sha256=xEjqBryCbzBpkzYhwRZht7-uyXv7XaCkEcRt6sjwE7k,783
|
|
20
|
-
j_perm/ops/_exec.py,sha256=HaFy__49QXnxYNbh1IdIu8ZQoWlQsrms5HgyRN5lV6o,1913
|
|
21
|
-
j_perm/ops/_if.py,sha256=DNYp0j3-3YYMIR_CcDM3Ai0CpW2Qt4SYtKxk-9onu8c,1590
|
|
22
|
-
j_perm/ops/copy.py,sha256=fV2rE92JyTRqo91GjXwWucklDR1T1ZcFHHbxG1F8S9g,1110
|
|
23
|
-
j_perm/ops/copy_d.py,sha256=2SGHoq4vaD79diJos_oO3pvZvC_XrQorxhXAgZ5l09E,1017
|
|
24
|
-
j_perm/ops/delete.py,sha256=7SzLE9W_dlmcLOVzKjOzIFLSAHf9nGlagbhbwIH_PmI,916
|
|
25
|
-
j_perm/ops/distinct.py,sha256=RoVIIRNm7C9iAjcPZHpoMfgKw6pGx7nuLkYwruhHeYY,1036
|
|
26
|
-
j_perm/ops/foreach.py,sha256=6tEQ9PyiWBvrm0eANg18p5hV-OvIiL2O1rdqWOC18tQ,1425
|
|
27
|
-
j_perm/ops/replace_root.py,sha256=FFyc-95n3kTzcOXrnjwDdvwOnKNitXKb_1F25MMnXd8,445
|
|
28
|
-
j_perm/ops/set.py,sha256=-kFFgHxeq65K5vLzDe6PhUoLT-ksoZYUVL1Jxf8bRA0,1882
|
|
29
|
-
j_perm/ops/update.py,sha256=gvJTh3vS4zD-lpaqN23w5OO3hTtkSdu8RPy0V8r8Rwg,2471
|
|
30
|
-
j_perm/schema/__init__.py,sha256=mhEbGdi1MgLeEm1IL8-tkTtbIdrdlvQi22sBr7vCUqg,3392
|
|
31
|
-
j_perm/shorthands/__init__.py,sha256=zvsu15iExYtUrct6Ob4IRs6Ao46PoZaCKVIUhMBY4Dk,117
|
|
32
|
-
j_perm/shorthands/_assert.py,sha256=iRvuUGlN5Bj9G5Bo-aXUa4MQujgmrR3N-PhDeee6xcY,555
|
|
33
|
-
j_perm/shorthands/assign_or_append.py,sha256=TsSzaQXb5qoKtY6tvx_skHob-695Zii5roUfXltznAQ,621
|
|
34
|
-
j_perm/shorthands/delete.py,sha256=PZeTRcP6-Q3SdbdyruWWXtMJ9mTnz1CBCHiOpHNI06Q,311
|
|
35
|
-
j_perm/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
j_perm/utils/pointers.py,sha256=X1HSCag4t-qq5zTvBMRAlBKXwpHVNNZYJrdJRtyEr4g,2788
|
|
37
|
-
j_perm-0.2.1.1.dist-info/METADATA,sha256=zwwrX08mxWxT5sMGLkh4PaLBVs7FMspDzwhcqj8sfpg,17660
|
|
38
|
-
j_perm-0.2.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
39
|
-
j_perm-0.2.1.1.dist-info/top_level.txt,sha256=yveBxREqVn9DliNwmen1QWoxR0uta7bU5giS1CCffRI,7
|
|
40
|
-
j_perm-0.2.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|