ExactMatcher

Bases: Matcher

Exact matcher

Source code in pzp/matcher.py
45
46
47
48
49
50
51
52
class ExactMatcher(Matcher, option="exact"):
    """
    Exact matcher
    """

    def filter(self, pattern: str, candidates: Sequence[Any], format_fn: Callable[[Any], str] = lambda x: str(x)) -> Sequence[Any]:
        pattern = pattern.lower()
        return [item for item in candidates if pattern in format_fn(item).lower()]

ExtendedMatcher

Bases: Matcher

Extended Matcher

This matcher accept multiple patterns delimited by spaces, such as: term ^start end$ !not

If patter is prefixed by a single-quote character ', it will not be splitted by spaces.

A backslash can be prepend to a space to match a literal space character.

A term can be prefixed by ^, or suffixed by $ to become an anchored-match term. Then matcher will search for the lines that start with or end with the given string.

If a term is prefixed by !, the matcher will exclude the lines that satisfy the term from the result.

Source code in pzp/matcher.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class ExtendedMatcher(Matcher, option="extended"):
    """
    Extended Matcher

    This matcher accept multiple patterns delimited by spaces, such as: term ^start end$ !not

    If patter is prefixed by a single-quote character ', it will not be splitted by spaces.

    A backslash can be prepend to a space to match a literal space character.

    A term can be prefixed by ^, or suffixed by $ to become an anchored-match term.
    Then matcher will search for the lines that start with or end with the given string.

    If a term is prefixed by !, the matcher will exclude the lines that satisfy the term from the result.
    """

    def filter(self, pattern: str, candidates: Sequence[Any], format_fn: Callable[[Any], str] = lambda x: str(x)) -> Sequence[Any]:
        # Prepare the filters
        filters = [ExtendedMatcherFilter(term) for term in self.split_pattern(pattern)]
        # Filter items
        return [item for item in candidates if self.filter_item(filters, item, format_fn)]

    def filter_item(
        self, filters: Sequence[ExtendedMatcherFilter], item: Any, format_fn: Callable[[Any], str] = lambda x: str(x)
    ) -> bool:
        txt: str = format_fn(item).lower()
        return all([filter_fn(txt) for filter_fn in filters])

    def split_pattern(self, pattern: str) -> Sequence[str]:
        "Split a pattern into terms"
        if pattern.startswith("'") or pattern.startswith("!'"):  # quote - keep spaces
            return [pattern.lower()]
        else:
            return [term.replace("\\", "") for term in SPLIT_ESCAPED_RE.split(pattern.lower()) if term]

split_pattern(pattern)

Split a pattern into terms

Source code in pzp/matcher.py
148
149
150
151
152
153
def split_pattern(self, pattern: str) -> Sequence[str]:
    "Split a pattern into terms"
    if pattern.startswith("'") or pattern.startswith("!'"):  # quote - keep spaces
        return [pattern.lower()]
    else:
        return [term.replace("\\", "") for term in SPLIT_ESCAPED_RE.split(pattern.lower()) if term]

ExtendedMatcherFilter

Source code in pzp/matcher.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
class ExtendedMatcherFilter:
    def __init__(self, term: str) -> None:
        self.prefix, self.term, self.suffix = self.split(term)
        # If a term is prefixed by !, the filter will exclude the lines that satisfy the term
        if "!" in self.prefix:
            self.exclude = True
        else:
            self.exclude = False
        if not self.term:
            self.exclude = False
            self.fn = self.always
        elif "'" in self.prefix:
            # Quoted - Check if the text contains the given term
            self.fn = self.in_match
        elif "^" in self.prefix:
            # If a term is prefixed by ^, the filter will include the lines than start with the given term
            if "$" in self.suffix:
                self.fn = self.exact_match
            else:
                self.fn = self.startswith_match
        elif "$" in self.suffix:
            # If a term is suffixed by $, the filter will include the lines than end with the given term
            self.fn = self.endswith_match
        else:
            # Check if the text contains the given term
            self.fn = self.in_match

    @classmethod
    def split(cls, term: str) -> Tuple[str, str, str]:
        "Split term in prefix, term, suffix"
        term, prefix = first_or_default([(term[len(p) :], p) for p in PREFIXES if term.startswith(p)], (term, ""))
        term, suffix = first_or_default([(term[: len(term) - len(s)], s) for s in SUFFIXES if term.endswith(s)], (term, ""))
        return prefix, term, suffix

    def __call__(self, txt: str) -> bool:
        "Evaluate the filter on the given text"
        if self.exclude:
            return not self.fn(txt)
        else:
            return self.fn(txt)

    def startswith_match(self, txt: str) -> bool:
        "True if the text startswith the given term"
        return txt.startswith(self.term)

    def endswith_match(self, txt: str) -> bool:
        "True if the text endswith the given term"
        return txt.endswith(self.term)

    def in_match(self, txt: str) -> bool:
        "True if the text contains the given term"
        return self.term in txt

    def exact_match(self, txt: str) -> bool:
        "Exact match"
        return self.term == txt

    def always(self, txt: str) -> bool:
        "Always return True"
        return True

    def __str__(self) -> str:
        return f"<{self.prefix}, {self.term}, {self.suffix}>"  # pragma: no cover

__call__(txt)

Evaluate the filter on the given text

Source code in pzp/matcher.py
89
90
91
92
93
94
def __call__(self, txt: str) -> bool:
    "Evaluate the filter on the given text"
    if self.exclude:
        return not self.fn(txt)
    else:
        return self.fn(txt)

always(txt)

Always return True

Source code in pzp/matcher.py
112
113
114
def always(self, txt: str) -> bool:
    "Always return True"
    return True

endswith_match(txt)

True if the text endswith the given term

Source code in pzp/matcher.py
100
101
102
def endswith_match(self, txt: str) -> bool:
    "True if the text endswith the given term"
    return txt.endswith(self.term)

exact_match(txt)

Exact match

Source code in pzp/matcher.py
108
109
110
def exact_match(self, txt: str) -> bool:
    "Exact match"
    return self.term == txt

in_match(txt)

True if the text contains the given term

Source code in pzp/matcher.py
104
105
106
def in_match(self, txt: str) -> bool:
    "True if the text contains the given term"
    return self.term in txt

split(term) classmethod

Split term in prefix, term, suffix

Source code in pzp/matcher.py
82
83
84
85
86
87
@classmethod
def split(cls, term: str) -> Tuple[str, str, str]:
    "Split term in prefix, term, suffix"
    term, prefix = first_or_default([(term[len(p) :], p) for p in PREFIXES if term.startswith(p)], (term, ""))
    term, suffix = first_or_default([(term[: len(term) - len(s)], s) for s in SUFFIXES if term.endswith(s)], (term, ""))
    return prefix, term, suffix

startswith_match(txt)

True if the text startswith the given term

Source code in pzp/matcher.py
96
97
98
def startswith_match(self, txt: str) -> bool:
    "True if the text startswith the given term"
    return txt.startswith(self.term)

Matcher

Bases: ABC

Source code in pzp/matcher.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Matcher(ABC):
    def __init_subclass__(cls, option: str, **kwargs: Dict[str, Any]) -> None:
        "Register a subclass"
        super().__init_subclass__(**kwargs)
        matchers[option] = cls

    @abstractmethod
    def filter(self, pattern: str, candidates: Sequence[Any], format_fn: Callable[[Any], str] = lambda x: str(x)) -> Sequence[Any]:
        """
        Filter candidates according to the given pattern

        Args:
            pattern: Pattern
            candidates: Candidates
            format_fn: Items format function

        Returns:
            result: Filtered candidates
        """
        pass  # pragma: no cover

__init_subclass__(option, **kwargs)

Register a subclass

Source code in pzp/matcher.py
24
25
26
27
def __init_subclass__(cls, option: str, **kwargs: Dict[str, Any]) -> None:
    "Register a subclass"
    super().__init_subclass__(**kwargs)
    matchers[option] = cls

filter(pattern, candidates, format_fn=lambda x: str(x)) abstractmethod

Filter candidates according to the given pattern

Parameters:

Name Type Description Default
pattern str

Pattern

required
candidates Sequence[Any]

Candidates

required
format_fn Callable[[Any], str]

Items format function

lambda x: str(x)

Returns:

Name Type Description
result Sequence[Any]

Filtered candidates

Source code in pzp/matcher.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@abstractmethod
def filter(self, pattern: str, candidates: Sequence[Any], format_fn: Callable[[Any], str] = lambda x: str(x)) -> Sequence[Any]:
    """
    Filter candidates according to the given pattern

    Args:
        pattern: Pattern
        candidates: Candidates
        format_fn: Items format function

    Returns:
        result: Filtered candidates
    """
    pass  # pragma: no cover

get_matcher(matcher)

Get a matcher instance by name or by class

Source code in pzp/matcher.py
156
157
158
159
160
161
162
163
def get_matcher(matcher: Union[Matcher, Type[Matcher], str]) -> Matcher:
    "Get a matcher instance by name or by class"
    if isinstance(matcher, Matcher):
        return matcher
    elif isinstance(matcher, str):
        return matchers[matcher]()
    else:
        return matcher()

list_matchers()

List matchers

Source code in pzp/matcher.py
166
167
168
def list_matchers() -> Sequence[str]:
    "List matchers"
    return list(matchers.keys())