/usr/lib/python3/dist-packages/behave/matchers.py is in python3-behave 1.2.5-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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 118 119 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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | import re
import parse
from parse_type import cfparse
from behave import model
class Matcher(object):
"""Pull parameters out of step names.
.. attribute:: string
The match pattern attached to the step function.
.. attribute:: func
The step function the pattern is being attached to.
"""
schema = "@%s('%s')" # Schema used to describe step definition (matcher)
def __init__(self, func, string, step_type=None):
self.func = func
self.string = string
self.step_type = step_type
self._location = None
@property
def location(self):
if self._location is None:
self._location = model.Match.make_location(self.func)
return self._location
def describe(self, schema=None):
"""Provide a textual description of the step function/matcher object.
:param schema: Text schema to use.
:return: Textual description of this step definition (matcher).
"""
step_type = self.step_type or 'step'
if not schema:
schema = self.schema
return schema % (step_type, self.string)
def check_match(self, step):
"""Match me against the "step" name supplied.
Return None if I don't match otherwise return a list of matches as
:class:`behave.model.Argument` instances.
The return value from this function will be converted into a
:class:`behave.model.Match` instance by *behave*.
"""
raise NotImplementedError
def match(self, step):
result = self.check_match(step)
if result is None:
return None
return model.Match(self.func, result)
def __repr__(self):
return "<%s: %r>" % (self.__class__.__name__, self.string)
class ParseMatcher(Matcher):
custom_types = {}
def __init__(self, func, string, step_type=None):
super(ParseMatcher, self).__init__(func, string, step_type)
self.parser = parse.compile(self.string, self.custom_types)
def check_match(self, step):
result = self.parser.parse(step)
if not result:
return None
args = []
for index, value in enumerate(result.fixed):
start, end = result.spans[index]
args.append(model.Argument(start, end, step[start:end], value))
for name, value in list(result.named.items()):
start, end = result.spans[name]
args.append(model.Argument(start, end, step[start:end], value, name))
args.sort(key=lambda x: x.start)
return args
class CFParseMatcher(ParseMatcher):
"""
Uses :class:`parse_type.cfparse.Parser` instead of "parse.Parser".
Provides support for automatic generation of type variants
for fields with CardinalityField part.
"""
def __init__(self, func, string, step_type=None):
super(ParseMatcher, self).__init__(func, string, step_type)
self.parser = cfparse.Parser(self.string, self.custom_types)
def register_type(**kw):
"""Registers a custom type that will be available to "parse"
for type conversion during step matching.
Converters should be supplied as ``name=callable`` arguments (or as dict).
A type converter should follow :pypi:`parse` module rules.
In general, a type converter is a function that converts text (as string)
into a value-type (type converted value).
EXAMPLE:
.. code-block:: python
from behave import register_type, given
import parse
# -- TYPE CONVERTER: For a simple, positive integer number.
@parse.with_pattern(r"\d+")
def parse_number(text):
return int(text)
# -- REGISTER TYPE-CONVERTER: With behave
register_type(Number=parse_number)
# -- STEP DEFINITIONS: Use type converter.
@given('{amount:Number} vehicles')
def step_impl(context, amount):
assert isinstance(amount, int)
"""
ParseMatcher.custom_types.update(kw)
class RegexMatcher(Matcher):
def __init__(self, func, string, step_type=None):
super(RegexMatcher, self).__init__(func, string, step_type)
assert not (string.startswith("^") or string.endswith("$")), \
"Regular expression should not use begin/end-markers: "+ string
expression = "^%s$" % self.string
self.regex = re.compile(expression)
def check_match(self, step):
m = self.regex.match(step)
if not m:
return None
groupindex = dict((y, x) for x, y in list(self.regex.groupindex.items()))
args = []
for index, group in enumerate(m.groups()):
index += 1
name = groupindex.get(index, None)
args.append(model.Argument(m.start(index), m.end(index), group,
group, name))
return args
matcher_mapping = {
"parse": ParseMatcher,
"cfparse": CFParseMatcher,
"re": RegexMatcher,
}
current_matcher = ParseMatcher
def use_step_matcher(name):
"""Change the parameter matcher used in parsing step text.
The change is immediate and may be performed between step definitions in
your step implementation modules - allowing adjacent steps to use different
matchers if necessary.
There are several parsers available in *behave* (by default):
**parse** (the default, based on: :pypi:`parse`)
Provides a simple parser that replaces regular expressions for
step parameters with a readable syntax like ``{param:Type}``.
The syntax is inspired by the Python builtin ``string.format()``
function.
Step parameters must use the named fields syntax of :pypi:`parse`
in step definitions. The named fields are extracted,
optionally type converted and then used as step function arguments.
Supports type conversions by using type converters
(see :func:`~behave.register_type()`).
**cfparse** (extends: :pypi:`parse`, requires: :pypi:`parse_type`)
Provides an extended parser with "Cardinality Field" (CF) support.
Automatically creates missing type converters for related cardinality
as long as a type converter for cardinality=1 is provided.
Supports parse expressions like:
* ``{values:Type+}`` (cardinality=1..N, many)
* ``{values:Type*}`` (cardinality=0..N, many0)
* ``{value:Type?}`` (cardinality=0..1, optional)
Supports type conversions (as above).
**re**
This uses full regular expressions to parse the clause text. You will
need to use named groups "(?P<name>...)" to define the variables pulled
from the text and passed to your ``step()`` function.
Type conversion is **not supported**.
A step function writer may implement type conversion
inside the step function (implementation).
You may `define your own matcher`_.
.. _`define your own matcher`: api.html#step-parameters
"""
global current_matcher
current_matcher = matcher_mapping[name]
def step_matcher(name):
"""
DEPRECATED, use :func:`use_step_matcher()` instead.
"""
# -- BACKWARD-COMPATIBLE NAME: Mark as deprecated.
import warnings
warnings.warn("Use 'use_step_matcher()' instead",
PendingDeprecationWarning, stacklevel=2)
use_step_matcher(name)
def get_matcher(func, string):
return current_matcher(func, string)
|