dotted-notation 0.44.6__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.
Files changed (38) hide show
  1. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/CHANGELOG.md +19 -0
  2. {dotted_notation-0.44.6/dotted_notation.egg-info → dotted_notation-0.44.7}/PKG-INFO +12 -1
  3. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/README.md +11 -0
  4. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/api.py +8 -2
  5. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/base.py +6 -5
  6. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/groups.py +16 -4
  7. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/recursive.py +1 -1
  8. {dotted_notation-0.44.6 → dotted_notation-0.44.7/dotted_notation.egg-info}/PKG-INFO +12 -1
  9. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/pyproject.toml +1 -1
  10. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/LICENSE +0 -0
  11. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/MANIFEST.in +0 -0
  12. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/__init__.py +0 -0
  13. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/__main__.py +0 -0
  14. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/access.py +0 -0
  15. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/cli/__init__.py +0 -0
  16. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/cli/_compat.py +0 -0
  17. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/cli/formats.py +0 -0
  18. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/cli/main.py +0 -0
  19. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/containers.py +0 -0
  20. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/engine.py +0 -0
  21. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/filters.py +0 -0
  22. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/grammar.py +0 -0
  23. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/matchers.py +0 -0
  24. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/predicates.py +0 -0
  25. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/results.py +0 -0
  26. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/sql/__init__.py +0 -0
  27. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/sql/core.py +0 -0
  28. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/sql/pg.py +0 -0
  29. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/transforms.py +0 -0
  30. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/utils.py +0 -0
  31. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/utypes.py +0 -0
  32. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted/wrappers.py +0 -0
  33. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted_notation.egg-info/SOURCES.txt +0 -0
  34. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted_notation.egg-info/dependency_links.txt +0 -0
  35. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted_notation.egg-info/entry_points.txt +0 -0
  36. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted_notation.egg-info/requires.txt +0 -0
  37. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/dotted_notation.egg-info/top_level.txt +0 -0
  38. {dotted_notation-0.44.6 → dotted_notation-0.44.7}/setup.cfg +0 -0
@@ -3,6 +3,25 @@
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
+
6
25
  ## [0.44.6]
7
26
 
8
27
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.44.6
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
 
@@ -762,6 +762,13 @@ def match(pattern, path, groups=False, partial=True, strict=False):
762
762
  'b'
763
763
  >>> match('(!a)', 'a')
764
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
+
765
772
  Recursive patterns:
766
773
  >>> match('**.c', 'a.b.c')
767
774
  'a.b.c'
@@ -789,8 +796,7 @@ def match(pattern, path, groups=False, partial=True, strict=False):
789
796
  result = base.match_ops(list(pats), list(path_ops), partial)
790
797
  if result is None:
791
798
  return returns(None, [])
792
- # TODO: pattern-only filtering for variadic matches
793
- return returns(path, result)
799
+ return returns(path, [v for v, _ in result], [p for _, p in result])
794
800
 
795
801
  # Original non-recursive match logic
796
802
  _matches = []
@@ -45,9 +45,9 @@ def has_any(gen):
45
45
  def match_ops(pats, path_ops, partial):
46
46
  """
47
47
  Match a list of pattern ops against a list of path ops.
48
- Returns a list of match values on success, None on failure.
49
- Dispatches to each op's do_match, which decides how many path
50
- segments it consumes.
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
51
  """
52
52
  if pats:
53
53
  return pats[0].do_match(pats[1:], path_ops, partial)
@@ -221,9 +221,10 @@ class TraversalOp(Op):
221
221
  rest = match_ops(rest_pats, path_ops[1:], partial)
222
222
  if rest is None:
223
223
  return None
224
+ is_pat = self.is_pattern()
224
225
  if isinstance(m, (tuple, list)):
225
- return [_m.val for _m in m] + rest
226
- return [m.val] + rest
226
+ return [(_m.val, is_pat) for _m in m] + rest
227
+ return [(m.val, is_pat)] + rest
227
228
 
228
229
 
229
230
  class MatchOp(Op):
@@ -109,11 +109,23 @@ class OpGroup(base.TraversalOp):
109
109
  concrete path can only be produced by one branch at a time, so
110
110
  disjunction, first-match, and conjunction all reduce to "any branch
111
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).
112
116
  """
117
+ from . import results
113
118
  for branch in base.branches_only(self.branches):
114
- result = base.match_ops(list(branch) + list(rest_pats), path_ops, partial)
115
- if result is not None:
116
- return result
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
117
129
  return None
118
130
 
119
131
  def _render(self, top=True):
@@ -552,7 +564,7 @@ class OpGroupNot(OpGroup):
552
564
  if rest is None:
553
565
  return None
554
566
  seg_val = getattr(getattr(kop, 'op', kop), 'value', kop)
555
- return [seg_val] + rest
567
+ return [(seg_val, True)] + rest
556
568
 
557
569
  def do_update(self, ops, node, val, has_defaults, _path, nop, nop_from_unwrap=False, **kwargs):
558
570
  inner = self.inner
@@ -133,7 +133,7 @@ class Recursive(BaseOp):
133
133
  if rest is None:
134
134
  continue
135
135
  combined = results.assemble(path_ops[:n])
136
- return [combined] + rest
136
+ return [(combined, True)] + rest
137
137
  return None
138
138
 
139
139
  def _effective_branches(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dotted_notation
3
- Version: 0.44.6
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.6"
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"