j-perm 0.2.1.1__tar.gz → 0.2.2__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 (46) hide show
  1. {j_perm-0.2.1.1 → j_perm-0.2.2}/PKG-INFO +1 -1
  2. {j_perm-0.2.1.1 → j_perm-0.2.2}/pyproject.toml +1 -1
  3. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/ref.py +1 -2
  4. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/engine.py +2 -0
  5. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/normalizer.py +1 -0
  6. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_assert.py +1 -2
  7. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_assert_d.py +1 -2
  8. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_exec.py +1 -2
  9. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/_if.py +1 -2
  10. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/copy.py +1 -2
  11. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/copy_d.py +1 -2
  12. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/delete.py +1 -2
  13. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/distinct.py +2 -3
  14. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/foreach.py +1 -2
  15. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/set.py +2 -3
  16. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/update.py +1 -2
  17. j_perm-0.2.2/src/j_perm/pointers.py +107 -0
  18. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/subst.py +1 -3
  19. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/PKG-INFO +1 -1
  20. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/SOURCES.txt +2 -3
  21. j_perm-0.2.1.1/src/j_perm/utils/__init__.py +0 -0
  22. j_perm-0.2.1.1/src/j_perm/utils/pointers.py +0 -108
  23. {j_perm-0.2.1.1 → j_perm-0.2.2}/README.md +0 -0
  24. {j_perm-0.2.1.1 → j_perm-0.2.2}/setup.cfg +0 -0
  25. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/__init__.py +0 -0
  26. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/__init__.py +0 -0
  27. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_bool.py +0 -0
  28. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_float.py +0 -0
  29. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_int.py +0 -0
  30. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/casters/_str.py +0 -0
  31. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/__init__.py +0 -0
  32. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/constructs/eval.py +0 -0
  33. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/funcs/__init__.py +0 -0
  34. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/funcs/subtract.py +0 -0
  35. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/op_handler.py +0 -0
  36. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/__init__.py +0 -0
  37. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/ops/replace_root.py +0 -0
  38. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/schema/__init__.py +0 -0
  39. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/__init__.py +0 -0
  40. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/_assert.py +0 -0
  41. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/assign_or_append.py +0 -0
  42. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/shorthands/delete.py +0 -0
  43. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm/special_resolver.py +0 -0
  44. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/dependency_links.txt +0 -0
  45. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/requires.txt +0 -0
  46. {j_perm-0.2.1.1 → j_perm-0.2.2}/src/j_perm.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: j-perm
3
- Version: 0.2.1.1
3
+ Version: 0.2.2
4
4
  Summary: json permutation library
5
5
  Author-email: Roman <kuschanow@gmail.com>
6
6
  License: MIT
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "j-perm"
9
- version = "0.2.1.1"
9
+ version = "0.2.2"
10
10
  description = "json permutation library"
11
11
  authors = [
12
12
  { name = "Roman", email = "kuschanow@gmail.com" },
@@ -4,7 +4,6 @@ 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")
@@ -14,7 +13,7 @@ def of_ref(node: Mapping[str, Any], src: Mapping[str, Any], engine: "ActionEngin
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)
@@ -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,7 @@ 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)
51
53
 
52
54
  def apply_actions(
53
55
  self,
@@ -19,6 +19,7 @@ class ShorthandRegistry:
19
19
  cls._rules[name] = fn
20
20
  cls._default_priority[name] = priority
21
21
  return fn
22
+
22
23
  return deco
23
24
 
24
25
  @classmethod
@@ -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")
@@ -17,7 +16,7 @@ def op_assert(
17
16
  path = engine.substitutor.substitute(step["path"], src)
18
17
 
19
18
  try:
20
- current = jptr_get(src, path)
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
 
@@ -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")
@@ -17,7 +16,7 @@ def op_assert_d(
17
16
  path = engine.substitutor.substitute(step["path"], dest)
18
17
 
19
18
  try:
20
- current = jptr_get(dest, path)
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
 
@@ -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")
@@ -26,7 +25,7 @@ def op_exec(
26
25
  if has_from:
27
26
  actions_ptr = engine.substitutor.substitute(step["from"], src)
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)
@@ -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")
@@ -18,7 +17,7 @@ def op_if(
18
17
  if "path" in step:
19
18
  try:
20
19
  ptr = engine.substitutor.substitute(step["path"], src)
21
- current = maybe_slice(ptr, dest)
20
+ current = engine.pointer_manager.maybe_slice(ptr, dest)
22
21
  missing = False
23
22
  except Exception:
24
23
  current = None
@@ -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")
@@ -24,7 +23,7 @@ def op_copy(
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"])
@@ -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")
@@ -23,7 +22,7 @@ def op_copy_d(
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"])
@@ -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")
@@ -18,7 +17,7 @@ def op_delete(
18
17
  ignore = bool(step.get("ignore_missing", True))
19
18
 
20
19
  try:
21
- parent, leaf = jptr_ensure_parent(dest, path, create=False)
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")
@@ -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")
@@ -15,7 +14,7 @@ def op_distinct(
15
14
  ) -> MutableMapping[str, Any]:
16
15
  """Remove duplicates from a list at the given path, preserving order."""
17
16
  path = engine.substitutor.substitute(step["path"], src)
18
- lst = jptr_get(dest, path)
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)")
@@ -27,7 +26,7 @@ def op_distinct(
27
26
  unique = []
28
27
  for item in lst:
29
28
  if key is not None:
30
- filter_item = jptr_get(item, key_path)
29
+ filter_item = engine.pointer_manager.get_pointer(item, key_path)
31
30
  else:
32
31
  filter_item = item
33
32
 
@@ -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")
@@ -21,7 +20,7 @@ def op_foreach(
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
 
@@ -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")
@@ -22,12 +21,12 @@ def op_set(
22
21
  if isinstance(value, (str, list, Mapping)):
23
22
  value = engine.substitutor.substitute(value, src)
24
23
 
25
- parent, leaf = jptr_ensure_parent(dest, path, create=create)
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 = jptr_ensure_parent(dest, path.rsplit("/", 1)[0], create=True)
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] = []
@@ -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")
@@ -22,7 +21,7 @@ def op_update(
22
21
  if "from" in step:
23
22
  ptr = engine.substitutor.substitute(step["from"], src)
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"])
@@ -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
@@ -8,8 +8,6 @@ from typing import Any, Callable, Mapping
8
8
  import jmespath
9
9
  from jmespath import functions as _jp_funcs
10
10
 
11
- from j_perm.utils.pointers import maybe_slice
12
-
13
11
  # =============================================================================
14
12
  # Types
15
13
  # =============================================================================
@@ -295,6 +293,6 @@ class TemplateSubstitutor:
295
293
  # 4) JSON Pointer fallback
296
294
  pointer = "/" + expr.lstrip("/")
297
295
  try:
298
- return maybe_slice(pointer, data) # type: ignore[arg-type]
296
+ return engine.pointer_manager.maybe_slice(pointer, data) # type: ignore[arg-type]
299
297
  except Exception:
300
298
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: j-perm
3
- Version: 0.2.1.1
3
+ Version: 0.2.2
4
4
  Summary: json permutation library
5
5
  Author-email: Roman <kuschanow@gmail.com>
6
6
  License: MIT
@@ -4,6 +4,7 @@ src/j_perm/__init__.py
4
4
  src/j_perm/engine.py
5
5
  src/j_perm/normalizer.py
6
6
  src/j_perm/op_handler.py
7
+ src/j_perm/pointers.py
7
8
  src/j_perm/special_resolver.py
8
9
  src/j_perm/subst.py
9
10
  src/j_perm.egg-info/PKG-INFO
@@ -38,6 +39,4 @@ src/j_perm/schema/__init__.py
38
39
  src/j_perm/shorthands/__init__.py
39
40
  src/j_perm/shorthands/_assert.py
40
41
  src/j_perm/shorthands/assign_or_append.py
41
- src/j_perm/shorthands/delete.py
42
- src/j_perm/utils/__init__.py
43
- src/j_perm/utils/pointers.py
42
+ src/j_perm/shorthands/delete.py
File without changes
@@ -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
File without changes
File without changes
File without changes