dotted-notation 0.44.5__tar.gz → 0.44.7__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.
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/CHANGELOG.md +43 -0
- {dotted_notation-0.44.5/dotted_notation.egg-info → dotted_notation-0.44.7}/PKG-INFO +12 -1
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/README.md +11 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/api.py +28 -52
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/base.py +41 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/groups.py +48 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/recursive.py +22 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/wrappers.py +12 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7/dotted_notation.egg-info}/PKG-INFO +12 -1
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/pyproject.toml +1 -1
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/LICENSE +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/MANIFEST.in +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/__init__.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/__main__.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/access.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/cli/__init__.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/cli/_compat.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/cli/formats.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/cli/main.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/containers.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/engine.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/filters.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/grammar.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/matchers.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/predicates.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/results.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/sql/__init__.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/sql/core.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/sql/pg.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/transforms.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/utils.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted/utypes.py +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/SOURCES.txt +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/dependency_links.txt +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/entry_points.txt +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/requires.txt +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/top_level.txt +0 -0
- {dotted_notation-0.44.5 → dotted_notation-0.44.7}/setup.cfg +0 -0
|
@@ -3,6 +3,49 @@
|
|
|
3
3
|
All notable changes to `dotted` are recorded here. Versions prior to
|
|
4
4
|
the ones listed are omitted — browse git history for earlier entries.
|
|
5
5
|
|
|
6
|
+
## [0.44.7]
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- `groups='patterns'` capture positions for variadic patterns: literals
|
|
10
|
+
are now correctly excluded when the pattern contains a group or
|
|
11
|
+
recursive op, so `translate`'s `$N` numbering counts pattern segments
|
|
12
|
+
only, as documented. Previously `a.(x,y).*` numbered `$0='a'` (a
|
|
13
|
+
literal), and `**.c` captured the literal `c`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- A group is one pattern segment: it captures the path segments its
|
|
17
|
+
matching branch consumed as a single group, keeping `$N` positions
|
|
18
|
+
stable across branches of different lengths. `x.(a.b,c)` matching
|
|
19
|
+
`x.a.b` now captures `('x', 'a.b')` instead of `('x', 'a', 'b')`.
|
|
20
|
+
Parentheses thereby act as a regex-like capture group: `x.(a.b)`
|
|
21
|
+
captures `'a.b'` as one group where `x.a.b` captures `'a', 'b'`.
|
|
22
|
+
Nested patterns inside a branch fold into the group's capture, as
|
|
23
|
+
with `**`.
|
|
24
|
+
|
|
25
|
+
## [0.44.6]
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `match()` supports op groups: disjunction `(a,b)`, first-match `(a,b)?`,
|
|
29
|
+
conjunction `(a&b)`, and negation `(!a)` now match paths instead of
|
|
30
|
+
raising `AttributeError`. A path matches a group if it matches any
|
|
31
|
+
branch followed by the rest of the pattern (a concrete path is only
|
|
32
|
+
ever one branch's output, so Or/First/And all reduce to this);
|
|
33
|
+
negation matches one segment its inner pattern does not.
|
|
34
|
+
Multi-segment branches, nested groups, recursive ops inside branches,
|
|
35
|
+
cut markers, and `groups=True` captures all supported.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- Wrapped variadic patterns in `match()`: `~(a,b)` (nop-wrapped group)
|
|
39
|
+
silently matched nothing and `**=7` (value-guarded recursive) raised
|
|
40
|
+
`AttributeError`; both now match.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- Path matching is polymorphic: `base.match_ops()` dispatches to each
|
|
44
|
+
op's `do_match`, which decides how many path segments it consumes
|
|
45
|
+
(single-segment default; backtracking on `Recursive`; branch
|
|
46
|
+
expansion on groups; `Wrap` delegates to variadic inners). The api
|
|
47
|
+
layer no longer special-cases op types.
|
|
48
|
+
|
|
6
49
|
## [0.44.5]
|
|
7
50
|
|
|
8
51
|
### Performance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.44.
|
|
3
|
+
Version: 0.44.7
|
|
4
4
|
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
5
|
Author-email: Frey Waid <logophage1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -508,6 +508,17 @@ excluding literals:
|
|
|
508
508
|
>>> dotted.match('hello.*', 'hello.there.bye', groups=GroupMode.patterns)
|
|
509
509
|
('hello.there.bye', ('there.bye',))
|
|
510
510
|
|
|
511
|
+
Op groups match like regex alternation, and parentheses act as a capture
|
|
512
|
+
group: a group captures the segments its matching branch consumed as a
|
|
513
|
+
single group, and counts as one pattern position:
|
|
514
|
+
|
|
515
|
+
>>> dotted.match('a.(x,y).*', 'a.x.z', groups=GroupMode.patterns, partial=False)
|
|
516
|
+
('a.x.z', ('x', 'z'))
|
|
517
|
+
>>> dotted.match('x.(a.b,c)', 'x.a.b', groups=True)
|
|
518
|
+
('x.a.b', ('x', 'a.b'))
|
|
519
|
+
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
520
|
+
('x.a.b', ('x', 'a.b'))
|
|
521
|
+
|
|
511
522
|
<a id="replace"></a>
|
|
512
523
|
### Replace
|
|
513
524
|
|
|
@@ -468,6 +468,17 @@ excluding literals:
|
|
|
468
468
|
>>> dotted.match('hello.*', 'hello.there.bye', groups=GroupMode.patterns)
|
|
469
469
|
('hello.there.bye', ('there.bye',))
|
|
470
470
|
|
|
471
|
+
Op groups match like regex alternation, and parentheses act as a capture
|
|
472
|
+
group: a group captures the segments its matching branch consumed as a
|
|
473
|
+
single group, and counts as one pattern position:
|
|
474
|
+
|
|
475
|
+
>>> dotted.match('a.(x,y).*', 'a.x.z', groups=GroupMode.patterns, partial=False)
|
|
476
|
+
('a.x.z', ('x', 'z'))
|
|
477
|
+
>>> dotted.match('x.(a.b,c)', 'x.a.b', groups=True)
|
|
478
|
+
('x.a.b', ('x', 'a.b'))
|
|
479
|
+
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
480
|
+
('x.a.b', ('x', 'a.b'))
|
|
481
|
+
|
|
471
482
|
<a id="replace"></a>
|
|
472
483
|
### Replace
|
|
473
484
|
|
|
@@ -722,51 +722,6 @@ def remove_multi(obj, iterable, paths_only=True, mutable=True, strict=False, bin
|
|
|
722
722
|
return remove_if_multi(obj, iterable, paths_only=False, pred=None, mutable=mutable, strict=strict, bindings=bindings)
|
|
723
723
|
|
|
724
724
|
|
|
725
|
-
def _match_ops(pats, path_ops, partial):
|
|
726
|
-
"""
|
|
727
|
-
Recursive match of pattern ops against path ops.
|
|
728
|
-
Returns list of match values on success, None on failure.
|
|
729
|
-
Handles Recursive ops which can consume variable-length path segments.
|
|
730
|
-
"""
|
|
731
|
-
if not pats:
|
|
732
|
-
if not path_ops:
|
|
733
|
-
return []
|
|
734
|
-
if partial:
|
|
735
|
-
return []
|
|
736
|
-
return None
|
|
737
|
-
|
|
738
|
-
pop = pats[0]
|
|
739
|
-
rest_pats = pats[1:]
|
|
740
|
-
|
|
741
|
-
# Non-recursive op: consume exactly one path segment
|
|
742
|
-
if not pop.is_recursive():
|
|
743
|
-
if not path_ops:
|
|
744
|
-
return None
|
|
745
|
-
kop = path_ops[0]
|
|
746
|
-
m = pop.match(kop, specials=True)
|
|
747
|
-
if not m:
|
|
748
|
-
return None
|
|
749
|
-
rest_result = _match_ops(rest_pats, path_ops[1:], partial)
|
|
750
|
-
if rest_result is None:
|
|
751
|
-
return None
|
|
752
|
-
if isinstance(m, (tuple, list)):
|
|
753
|
-
return [_m.val for _m in m] + rest_result
|
|
754
|
-
return [m.val] + rest_result
|
|
755
|
-
|
|
756
|
-
# Recursive op: try consuming 1, 2, ... N path segments via backtracking
|
|
757
|
-
for n in range(1, len(path_ops) + 1):
|
|
758
|
-
kop = path_ops[n - 1]
|
|
759
|
-
seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
|
|
760
|
-
matched = any(True for _ in pop.inner.matches((seg_val,)))
|
|
761
|
-
if not matched:
|
|
762
|
-
break # chain-following: stop extending once a segment fails
|
|
763
|
-
rest_result = _match_ops(rest_pats, path_ops[n:], partial)
|
|
764
|
-
if rest_result is not None:
|
|
765
|
-
combined = results.assemble(path_ops[:n])
|
|
766
|
-
return [combined] + rest_result
|
|
767
|
-
return None
|
|
768
|
-
|
|
769
|
-
|
|
770
725
|
def match(pattern, path, groups=False, partial=True, strict=False):
|
|
771
726
|
"""
|
|
772
727
|
Returns `path` if `pattern` matches; otherwise `None`
|
|
@@ -791,6 +746,29 @@ def match(pattern, path, groups=False, partial=True, strict=False):
|
|
|
791
746
|
>>> match('hello.*', 'hello.there.bye', groups='patterns')
|
|
792
747
|
('hello.there.bye', ('there.bye',))
|
|
793
748
|
|
|
749
|
+
Group patterns match if any branch matches:
|
|
750
|
+
>>> match('(a,b)', 'a')
|
|
751
|
+
'a'
|
|
752
|
+
>>> match('(a,b)', 'c')
|
|
753
|
+
>>> match('x.(a,b)', 'x.b')
|
|
754
|
+
'x.b'
|
|
755
|
+
>>> match('(a.b,c)', 'a.b')
|
|
756
|
+
'a.b'
|
|
757
|
+
>>> match('(a,b)?', 'b')
|
|
758
|
+
'b'
|
|
759
|
+
>>> match('x(.a&.b)', 'x.a')
|
|
760
|
+
'x.a'
|
|
761
|
+
>>> match('(!a)', 'b')
|
|
762
|
+
'b'
|
|
763
|
+
>>> match('(!a)', 'a')
|
|
764
|
+
|
|
765
|
+
A group is one pattern segment: it captures the segments its branch
|
|
766
|
+
consumed as a single group, and counts as one pattern position:
|
|
767
|
+
>>> match('x.(a.b,c)', 'x.a.b', groups=True)
|
|
768
|
+
('x.a.b', ('x', 'a.b'))
|
|
769
|
+
>>> match('a.(x,y).*', 'a.x.z', groups='patterns', partial=False)
|
|
770
|
+
('a.x.z', ('x', 'z'))
|
|
771
|
+
|
|
794
772
|
Recursive patterns:
|
|
795
773
|
>>> match('**.c', 'a.b.c')
|
|
796
774
|
'a.b.c'
|
|
@@ -812,15 +790,13 @@ def match(pattern, path, groups=False, partial=True, strict=False):
|
|
|
812
790
|
pats = parse(pattern)
|
|
813
791
|
path_ops = parse(path)
|
|
814
792
|
|
|
815
|
-
#
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
result = _match_ops(list(pats), list(path_ops), partial)
|
|
793
|
+
# Variadic ops (recursive, groups) consume variable-length path
|
|
794
|
+
# segments — use the recursive matcher
|
|
795
|
+
if any(op.is_variadic() for op in pats):
|
|
796
|
+
result = base.match_ops(list(pats), list(path_ops), partial)
|
|
820
797
|
if result is None:
|
|
821
798
|
return returns(None, [])
|
|
822
|
-
|
|
823
|
-
return returns(path, result)
|
|
799
|
+
return returns(path, [v for v, _ in result], [p for _, p in result])
|
|
824
800
|
|
|
825
801
|
# Original non-recursive match logic
|
|
826
802
|
_matches = []
|
|
@@ -42,6 +42,22 @@ def has_any(gen):
|
|
|
42
42
|
return any(True for _ in gen)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def match_ops(pats, path_ops, partial):
|
|
46
|
+
"""
|
|
47
|
+
Match a list of pattern ops against a list of path ops.
|
|
48
|
+
Returns a list of (value, is_pattern) capture pairs on success, None
|
|
49
|
+
on failure. Dispatches to each op's do_match, which decides how
|
|
50
|
+
many path segments it consumes.
|
|
51
|
+
"""
|
|
52
|
+
if pats:
|
|
53
|
+
return pats[0].do_match(pats[1:], path_ops, partial)
|
|
54
|
+
if not path_ops:
|
|
55
|
+
return []
|
|
56
|
+
if partial:
|
|
57
|
+
return []
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
45
61
|
class MatchResult:
|
|
46
62
|
def __init__(self, val):
|
|
47
63
|
self.val = val
|
|
@@ -75,6 +91,12 @@ class Op:
|
|
|
75
91
|
return False
|
|
76
92
|
def is_slice(self):
|
|
77
93
|
return False
|
|
94
|
+
def is_variadic(self):
|
|
95
|
+
"""
|
|
96
|
+
True if this op can consume a variable number of path segments when
|
|
97
|
+
matching a path (recursive ops, groups).
|
|
98
|
+
"""
|
|
99
|
+
return False
|
|
78
100
|
|
|
79
101
|
|
|
80
102
|
class MetaNOP(type):
|
|
@@ -185,6 +207,25 @@ class TraversalOp(Op):
|
|
|
185
207
|
"""
|
|
186
208
|
return False
|
|
187
209
|
|
|
210
|
+
def do_match(self, rest_pats, path_ops, partial):
|
|
211
|
+
"""
|
|
212
|
+
Match this op against exactly one path segment, then continue with
|
|
213
|
+
the remaining pattern ops. Variadic ops (recursive, groups)
|
|
214
|
+
override to consume a variable number of segments.
|
|
215
|
+
"""
|
|
216
|
+
if not path_ops:
|
|
217
|
+
return None
|
|
218
|
+
m = self.match(path_ops[0], specials=True)
|
|
219
|
+
if not m:
|
|
220
|
+
return None
|
|
221
|
+
rest = match_ops(rest_pats, path_ops[1:], partial)
|
|
222
|
+
if rest is None:
|
|
223
|
+
return None
|
|
224
|
+
is_pat = self.is_pattern()
|
|
225
|
+
if isinstance(m, (tuple, list)):
|
|
226
|
+
return [(_m.val, is_pat) for _m in m] + rest
|
|
227
|
+
return [(m.val, is_pat)] + rest
|
|
228
|
+
|
|
188
229
|
|
|
189
230
|
class MatchOp(Op):
|
|
190
231
|
"""
|
|
@@ -32,6 +32,9 @@ class OpGroup(base.TraversalOp):
|
|
|
32
32
|
def is_pattern(self):
|
|
33
33
|
return True
|
|
34
34
|
|
|
35
|
+
def is_variadic(self):
|
|
36
|
+
return True
|
|
37
|
+
|
|
35
38
|
def is_template(self):
|
|
36
39
|
"""
|
|
37
40
|
True if any branch contains a template op.
|
|
@@ -99,6 +102,32 @@ class OpGroup(base.TraversalOp):
|
|
|
99
102
|
def operator(self, top=False):
|
|
100
103
|
return self._render(top)
|
|
101
104
|
|
|
105
|
+
def do_match(self, rest_pats, path_ops, partial):
|
|
106
|
+
"""
|
|
107
|
+
Match a concrete path against this group: the path matches if it
|
|
108
|
+
matches any branch followed by the remaining pattern ops. A single
|
|
109
|
+
concrete path can only be produced by one branch at a time, so
|
|
110
|
+
disjunction, first-match, and conjunction all reduce to "any branch
|
|
111
|
+
matches"; cut markers don't constrain matching either.
|
|
112
|
+
|
|
113
|
+
The group is one pattern segment: it contributes a single capture —
|
|
114
|
+
the path segments its branch consumed, assembled — flagged as a
|
|
115
|
+
pattern match (like Recursive).
|
|
116
|
+
"""
|
|
117
|
+
from . import results
|
|
118
|
+
for branch in base.branches_only(self.branches):
|
|
119
|
+
branch_ops = list(branch)
|
|
120
|
+
for n in range(len(path_ops) + 1):
|
|
121
|
+
consumed = base.match_ops(branch_ops, path_ops[:n], False)
|
|
122
|
+
if consumed is None:
|
|
123
|
+
continue
|
|
124
|
+
rest = base.match_ops(list(rest_pats), path_ops[n:], partial)
|
|
125
|
+
if rest is None:
|
|
126
|
+
continue
|
|
127
|
+
combined = results.assemble(path_ops[:n])
|
|
128
|
+
return [(combined, True)] + rest
|
|
129
|
+
return None
|
|
130
|
+
|
|
102
131
|
def _render(self, top=True):
|
|
103
132
|
"""
|
|
104
133
|
Render the group as a string. Subclasses override this.
|
|
@@ -518,6 +547,25 @@ class OpGroupNot(OpGroup):
|
|
|
518
547
|
stack.push(base.Frame(frame.ops, v, cp, kwargs=frame.kwargs))
|
|
519
548
|
return ()
|
|
520
549
|
|
|
550
|
+
def do_match(self, rest_pats, path_ops, partial):
|
|
551
|
+
"""
|
|
552
|
+
Match one path segment NOT matched by the inner pattern's first op,
|
|
553
|
+
then continue with the rest of the inner branch and remaining ops.
|
|
554
|
+
"""
|
|
555
|
+
inner = self.inner
|
|
556
|
+
if not inner:
|
|
557
|
+
return None
|
|
558
|
+
if not path_ops:
|
|
559
|
+
return None
|
|
560
|
+
kop = path_ops[0]
|
|
561
|
+
if base.match_ops([inner[0]], [kop], False) is not None:
|
|
562
|
+
return None
|
|
563
|
+
rest = base.match_ops(list(inner[1:]) + list(rest_pats), path_ops[1:], partial)
|
|
564
|
+
if rest is None:
|
|
565
|
+
return None
|
|
566
|
+
seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
|
|
567
|
+
return [(seg_val, True)] + rest
|
|
568
|
+
|
|
521
569
|
def do_update(self, ops, node, val, has_defaults, _path, nop, nop_from_unwrap=False, **kwargs):
|
|
522
570
|
inner = self.inner
|
|
523
571
|
if not inner:
|
|
@@ -64,6 +64,9 @@ class Recursive(BaseOp):
|
|
|
64
64
|
def is_recursive(self):
|
|
65
65
|
return True
|
|
66
66
|
|
|
67
|
+
def is_variadic(self):
|
|
68
|
+
return True
|
|
69
|
+
|
|
67
70
|
def operator(self, top=False):
|
|
68
71
|
if self.accessors is not None:
|
|
69
72
|
s = f'*({self._render_accessors()})'
|
|
@@ -114,6 +117,25 @@ class Recursive(BaseOp):
|
|
|
114
117
|
def match(self, op, specials=False):
|
|
115
118
|
return self.inner.matchable(op, specials=specials)
|
|
116
119
|
|
|
120
|
+
def do_match(self, rest_pats, path_ops, partial):
|
|
121
|
+
"""
|
|
122
|
+
Match by consuming 1..N path segments via backtracking. Each
|
|
123
|
+
consumed segment must match the inner pattern (chain-following);
|
|
124
|
+
stop extending once a segment fails.
|
|
125
|
+
"""
|
|
126
|
+
from . import results
|
|
127
|
+
for n in range(1, len(path_ops) + 1):
|
|
128
|
+
kop = path_ops[n - 1]
|
|
129
|
+
seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
|
|
130
|
+
if not any(True for _ in self.inner.matches((seg_val,))):
|
|
131
|
+
return None
|
|
132
|
+
rest = base.match_ops(rest_pats, path_ops[n:], partial)
|
|
133
|
+
if rest is None:
|
|
134
|
+
continue
|
|
135
|
+
combined = results.assemble(path_ops[:n])
|
|
136
|
+
return [(combined, True)] + rest
|
|
137
|
+
return None
|
|
138
|
+
|
|
117
139
|
def _effective_branches(self):
|
|
118
140
|
"""
|
|
119
141
|
Return accessor branches that drive recursion.
|
|
@@ -72,6 +72,18 @@ class Wrap(base.TraversalOp):
|
|
|
72
72
|
except TypeError:
|
|
73
73
|
return self.inner.match(op)
|
|
74
74
|
|
|
75
|
+
def is_variadic(self):
|
|
76
|
+
return self.inner.is_variadic() if hasattr(self.inner, 'is_variadic') else False
|
|
77
|
+
|
|
78
|
+
def do_match(self, rest_pats, path_ops, partial):
|
|
79
|
+
"""
|
|
80
|
+
Variadic inners (recursive ops, groups) decide their own segment
|
|
81
|
+
consumption; otherwise match one segment via this wrap's match.
|
|
82
|
+
"""
|
|
83
|
+
if self.is_variadic():
|
|
84
|
+
return self.inner.do_match(rest_pats, path_ops, partial)
|
|
85
|
+
return super().do_match(rest_pats, path_ops, partial)
|
|
86
|
+
|
|
75
87
|
def concrete(self, val):
|
|
76
88
|
return self.inner.concrete(val)
|
|
77
89
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotted_notation
|
|
3
|
-
Version: 0.44.
|
|
3
|
+
Version: 0.44.7
|
|
4
4
|
Summary: Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms
|
|
5
5
|
Author-email: Frey Waid <logophage1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -508,6 +508,17 @@ excluding literals:
|
|
|
508
508
|
>>> dotted.match('hello.*', 'hello.there.bye', groups=GroupMode.patterns)
|
|
509
509
|
('hello.there.bye', ('there.bye',))
|
|
510
510
|
|
|
511
|
+
Op groups match like regex alternation, and parentheses act as a capture
|
|
512
|
+
group: a group captures the segments its matching branch consumed as a
|
|
513
|
+
single group, and counts as one pattern position:
|
|
514
|
+
|
|
515
|
+
>>> dotted.match('a.(x,y).*', 'a.x.z', groups=GroupMode.patterns, partial=False)
|
|
516
|
+
('a.x.z', ('x', 'z'))
|
|
517
|
+
>>> dotted.match('x.(a.b,c)', 'x.a.b', groups=True)
|
|
518
|
+
('x.a.b', ('x', 'a.b'))
|
|
519
|
+
>>> dotted.match('x.(a.b)', 'x.a.b', groups=True)
|
|
520
|
+
('x.a.b', ('x', 'a.b'))
|
|
521
|
+
|
|
511
522
|
<a id="replace"></a>
|
|
512
523
|
### Replace
|
|
513
524
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dotted_notation"
|
|
7
|
-
version = "0.44.
|
|
7
|
+
version = "0.44.7"
|
|
8
8
|
description = "Dotted notation for safe nested data traversal with optional chaining, pattern matching, and transforms"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dotted_notation-0.44.5 → dotted_notation-0.44.7}/dotted_notation.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|