rebulk 4.2.1__tar.gz → 4.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.
- {rebulk-4.2.1 → rebulk-4.2.2}/PKG-INFO +1 -1
- {rebulk-4.2.1 → rebulk-4.2.2}/pyproject.toml +6 -1
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/__version__.py +1 -2
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/loose.py +3 -35
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/match.py +5 -1
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/pattern.py +23 -40
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/rebulk.py +0 -3
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_match.py +13 -1
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/toposort.py +7 -15
- {rebulk-4.2.1 → rebulk-4.2.2}/LICENSE +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/README.md +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/__init__.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/builder.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/chain.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/debug.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/formatters.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/introspector.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/key.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/processors.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/py.typed +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/remodule.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/rules.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/__init__.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/default_rules_module.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/rebulk_rules_module.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/rules_module.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_chain.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_debug.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_introspector.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_key.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_loose.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_model.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_pattern.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_processors.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_rebulk.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_rules.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_toposort.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_utils.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/test/test_validators.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/utils.py +0 -0
- {rebulk-4.2.1 → rebulk-4.2.2}/rebulk/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rebulk"
|
|
3
|
-
version = "4.2.
|
|
3
|
+
version = "4.2.2"
|
|
4
4
|
description = "Rebulk - Define simple search patterns in bulk to perform advanced matching on any string."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -105,6 +105,11 @@ select = [
|
|
|
105
105
|
python_version = "3.10"
|
|
106
106
|
files = ["rebulk"]
|
|
107
107
|
strict = true
|
|
108
|
+
|
|
109
|
+
[[tool.mypy.overrides]]
|
|
110
|
+
# `regex` (the optional `native` extra) ships no type information; scope the
|
|
111
|
+
# relaxation to it instead of silencing missing imports across the whole project.
|
|
112
|
+
module = ["regex.*"]
|
|
108
113
|
ignore_missing_imports = true
|
|
109
114
|
|
|
110
115
|
[tool.pytest.ini_options]
|
|
@@ -5,17 +5,9 @@ Various utilities functions
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from inspect import isclass
|
|
8
|
+
from inspect import getfullargspec, isclass
|
|
9
9
|
from typing import TYPE_CHECKING, Any, cast
|
|
10
10
|
|
|
11
|
-
try:
|
|
12
|
-
from inspect import getfullargspec as getargspec
|
|
13
|
-
|
|
14
|
-
_FULLARGSPEC_SUPPORTED = True
|
|
15
|
-
except ImportError:
|
|
16
|
-
_FULLARGSPEC_SUPPORTED = False
|
|
17
|
-
from inspect import getargspec # type: ignore[assignment] # removed in Python 3.11, dead on supported versions
|
|
18
|
-
|
|
19
11
|
from .utils import is_iterable
|
|
20
12
|
|
|
21
13
|
if TYPE_CHECKING:
|
|
@@ -66,7 +58,7 @@ def function_args(callable_: Any, *args: Any, **kwargs: Any) -> tuple[Any, Any]:
|
|
|
66
58
|
:return: (args, kwargs) matching the function signature
|
|
67
59
|
:rtype: tuple
|
|
68
60
|
"""
|
|
69
|
-
argspec =
|
|
61
|
+
argspec = getfullargspec(callable_)
|
|
70
62
|
return argspec_args(argspec, False, *args, **kwargs)
|
|
71
63
|
|
|
72
64
|
|
|
@@ -83,7 +75,7 @@ def constructor_args(class_: Any, *args: Any, **kwargs: Any) -> tuple[Any, Any]:
|
|
|
83
75
|
:return: (args, kwargs) matching the function signature
|
|
84
76
|
:rtype: tuple
|
|
85
77
|
"""
|
|
86
|
-
argspec =
|
|
78
|
+
argspec = getfullargspec(_constructor(class_))
|
|
87
79
|
return argspec_args(argspec, True, *args, **kwargs)
|
|
88
80
|
|
|
89
81
|
|
|
@@ -107,30 +99,6 @@ def argspec_args(argspec: FullArgSpec, constructor: bool, *args: Any, **kwargs:
|
|
|
107
99
|
return call_args, call_kwarg
|
|
108
100
|
|
|
109
101
|
|
|
110
|
-
if not _FULLARGSPEC_SUPPORTED:
|
|
111
|
-
|
|
112
|
-
def argspec_args_legacy(argspec: Any, constructor: bool, *args: Any, **kwargs: Any) -> tuple[Any, Any]:
|
|
113
|
-
"""
|
|
114
|
-
Return (args, kwargs) matching the argspec object
|
|
115
|
-
|
|
116
|
-
:param argspec: argspec to use
|
|
117
|
-
:type argspec: argspec
|
|
118
|
-
:param constructor: is it a constructor ?
|
|
119
|
-
:type constructor: bool
|
|
120
|
-
:param args:
|
|
121
|
-
:type args:
|
|
122
|
-
:param kwargs:
|
|
123
|
-
:type kwargs:
|
|
124
|
-
:return: (args, kwargs) matching the function signature
|
|
125
|
-
:rtype: tuple
|
|
126
|
-
"""
|
|
127
|
-
call_kwarg = kwargs if argspec.keywords else {k: kwargs[k] for k in kwargs if k in argspec.args}
|
|
128
|
-
call_args = args if argspec.varargs else args[: len(argspec.args) - (1 if constructor else 0)]
|
|
129
|
-
return call_args, call_kwarg
|
|
130
|
-
|
|
131
|
-
argspec_args = argspec_args_legacy
|
|
132
|
-
|
|
133
|
-
|
|
134
102
|
def ensure_list(param: Any) -> list[Any]:
|
|
135
103
|
"""
|
|
136
104
|
Retrieves a list from given parameter.
|
|
@@ -1164,7 +1164,11 @@ class Match:
|
|
|
1164
1164
|
return self.end - self.start
|
|
1165
1165
|
|
|
1166
1166
|
def __hash__(self) -> int:
|
|
1167
|
-
|
|
1167
|
+
# Hash on the span only (a subset of the __eq__ fields), never on the
|
|
1168
|
+
# mutable value: a match kept in a set/dict must stay findable after its
|
|
1169
|
+
# value is changed. Equal matches still share a hash; same-span matches
|
|
1170
|
+
# with different values collide harmlessly and __eq__ tells them apart.
|
|
1171
|
+
return hash((Match, self.start, self.end))
|
|
1168
1172
|
|
|
1169
1173
|
def __eq__(self, other: object) -> bool:
|
|
1170
1174
|
if isinstance(other, Match):
|
|
@@ -20,6 +20,11 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from collections.abc import Callable, Iterator, Sequence
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def _callable_or_none(value: Any) -> Callable[..., Any] | None:
|
|
24
|
+
"""Return ``value`` if it is callable, else ``None``."""
|
|
25
|
+
return value if callable(value) else None
|
|
26
|
+
|
|
27
|
+
|
|
23
28
|
class BasePattern(metaclass=ABCMeta):
|
|
24
29
|
"""
|
|
25
30
|
Base class for Pattern like objects
|
|
@@ -144,21 +149,9 @@ class Pattern(BasePattern, metaclass=ABCMeta):
|
|
|
144
149
|
self._log_level = log_level
|
|
145
150
|
self._properties = properties
|
|
146
151
|
self.defined_at = debug.defined_at()
|
|
147
|
-
self.post_processor
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
else:
|
|
151
|
-
self.post_processor = post_processor
|
|
152
|
-
self.pre_match_processor: Callable[..., Any] | None
|
|
153
|
-
if not callable(pre_match_processor):
|
|
154
|
-
self.pre_match_processor = None
|
|
155
|
-
else:
|
|
156
|
-
self.pre_match_processor = pre_match_processor
|
|
157
|
-
self.post_match_processor: Callable[..., Any] | None
|
|
158
|
-
if not callable(post_match_processor):
|
|
159
|
-
self.post_match_processor = None
|
|
160
|
-
else:
|
|
161
|
-
self.post_match_processor = post_match_processor
|
|
152
|
+
self.post_processor = _callable_or_none(post_processor)
|
|
153
|
+
self.pre_match_processor = _callable_or_none(pre_match_processor)
|
|
154
|
+
self.post_match_processor = _callable_or_none(post_match_processor)
|
|
162
155
|
|
|
163
156
|
@property
|
|
164
157
|
def log_level(self) -> int:
|
|
@@ -517,32 +510,22 @@ class RePattern(Pattern):
|
|
|
517
510
|
for i in range(1, pattern.groups + 1):
|
|
518
511
|
name = names.get(i, main_match.name)
|
|
519
512
|
if self.repeated_captures:
|
|
520
|
-
|
|
521
|
-
child_match = Match(
|
|
522
|
-
start,
|
|
523
|
-
end,
|
|
524
|
-
name=name,
|
|
525
|
-
parent=main_match,
|
|
526
|
-
pattern=self,
|
|
527
|
-
input_string=input_string,
|
|
528
|
-
**self._children_match_kwargs,
|
|
529
|
-
)
|
|
530
|
-
if child_match:
|
|
531
|
-
main_match.children.append(child_match)
|
|
513
|
+
spans = match_object.spans(i)
|
|
532
514
|
else:
|
|
533
|
-
|
|
534
|
-
if
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
515
|
+
span = match_object.span(i)
|
|
516
|
+
spans = [span] if span[0] > -1 and span[1] > -1 else []
|
|
517
|
+
for child_start, child_end in spans:
|
|
518
|
+
child_match = Match(
|
|
519
|
+
child_start,
|
|
520
|
+
child_end,
|
|
521
|
+
name=name,
|
|
522
|
+
parent=main_match,
|
|
523
|
+
pattern=self,
|
|
524
|
+
input_string=input_string,
|
|
525
|
+
**self._children_match_kwargs,
|
|
526
|
+
)
|
|
527
|
+
if child_match:
|
|
528
|
+
main_match.children.append(child_match)
|
|
546
529
|
|
|
547
530
|
if main_match:
|
|
548
531
|
yield main_match
|
|
@@ -189,9 +189,6 @@ class Rebulk(Builder):
|
|
|
189
189
|
pattern_matches = pattern.matches(cast("str", matches.input_string), context)
|
|
190
190
|
if pattern_matches:
|
|
191
191
|
log(pattern.log_level, "Pattern has %s match(es). (%s)", len(pattern_matches), pattern)
|
|
192
|
-
else:
|
|
193
|
-
pass
|
|
194
|
-
# log(pattern.log_level, "Pattern doesn't match. (%s)" % (pattern,))
|
|
195
192
|
for match in pattern_matches:
|
|
196
193
|
if match.marker:
|
|
197
194
|
log(pattern.log_level, "Marker found. (%s)", match)
|
|
@@ -51,12 +51,24 @@ class TestMatchClass:
|
|
|
51
51
|
other = object()
|
|
52
52
|
|
|
53
53
|
assert hash(match1) != hash(match2)
|
|
54
|
-
|
|
54
|
+
# same span, different value: hashes collide (hash must not depend on the
|
|
55
|
+
# mutable value); equality still tells them apart below.
|
|
56
|
+
assert hash(match1) == hash(match3)
|
|
55
57
|
|
|
56
58
|
assert match1 != other
|
|
57
59
|
assert match1 != match2
|
|
58
60
|
assert match1 != match3
|
|
59
61
|
|
|
62
|
+
def test_hash_is_stable_under_value_mutation(self) -> None:
|
|
63
|
+
match = Match(1, 3, value="es")
|
|
64
|
+
bucket = {match}
|
|
65
|
+
|
|
66
|
+
match.value = "changed"
|
|
67
|
+
|
|
68
|
+
# The hash must not change when the value is mutated, so the match stays
|
|
69
|
+
# findable in the set it was added to.
|
|
70
|
+
assert match in bucket
|
|
71
|
+
|
|
60
72
|
def test_length(self) -> None:
|
|
61
73
|
match1 = Match(0, 4, value="test")
|
|
62
74
|
match2 = Match(0, 2, value="spanIsUsed")
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
#
|
|
2
|
+
# Vendored from toposort 1.4 (https://bitbucket.org/ericvsmith/toposort),
|
|
3
|
+
# Copyright 2014 True Blade Systems, Inc., licensed under the Apache License,
|
|
4
|
+
# Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0).
|
|
3
5
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Original:
|
|
11
|
-
# - https://bitbucket.org/ericvsmith/toposort (1.4)
|
|
12
|
-
# Modifications:
|
|
13
|
-
# - merged Pull request #2 for CyclicDependency error
|
|
14
|
-
# - import reduce as original name
|
|
15
|
-
# - support python 2.6 dict comprehension
|
|
6
|
+
# Local changes: CyclicDependency error (upstream pull request #2), Python 3
|
|
7
|
+
# only, fully type-annotated.
|
|
16
8
|
|
|
17
9
|
from __future__ import annotations
|
|
18
10
|
|
|
@@ -27,7 +19,7 @@ _T = TypeVar("_T")
|
|
|
27
19
|
|
|
28
20
|
class CyclicDependency(ValueError):
|
|
29
21
|
def __init__(self, cyclic: dict[_T, set[_T]]) -> None:
|
|
30
|
-
s = "Cyclic dependencies exist among these items: {
|
|
22
|
+
s = f"Cyclic dependencies exist among these items: {', '.join(repr(x) for x in cyclic.items())}"
|
|
31
23
|
super().__init__(s)
|
|
32
24
|
self.cyclic = cyclic
|
|
33
25
|
|
|
@@ -38,7 +30,7 @@ def toposort(data: dict[_T, set[_T]]) -> Iterator[set[_T]]:
|
|
|
38
30
|
and whose values are a set of dependent items. Output is a list of
|
|
39
31
|
sets in topological order. The first set consists of items with no
|
|
40
32
|
dependences, each subsequent set consists of items that depend upon
|
|
41
|
-
items in the
|
|
33
|
+
items in the preceding sets.
|
|
42
34
|
:param data:
|
|
43
35
|
:type data:
|
|
44
36
|
:return:
|
|
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
|
|
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
|