Files
Iristyle a000ce8acc feat(ST2.UtilPackages): bump up all packages
- Refresh PackageCache with latest versions of everything
2013-09-16 22:35:46 -04:00

833 lines
29 KiB
Python

"""pysemver: Semantic Version comparing for Python.
Provides comparing of semantic versions by using SemVer objects using rich comperations plus the
possibility to match a selector string against versions. Interesting for version dependencies.
Versions look like: "1.7.12+b.133"
Selectors look like: ">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113"
Example usages:
>>> SemVer(1, 2, 3, build=13)
SemVer("1.2.3+13")
>>> SemVer.valid("1.2.3.4")
False
>>> SemVer.clean("this is unimportant text 1.2.3-2 and will be stripped")
"1.2.3-2"
>>> SemVer("1.7.12+b.133").satisfies(">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113")
True
>>> SemSel(">1.7.0 || 1.6.9+b.111 - 1.6.9+b.113").matches(SemVer("1.7.12+b.133"),
... SemVer("1.6.9+b.112"), SemVer("1.6.10"))
[SemVer("1.7.12+b.133"), SemVer("1.6.9+b.112")]
>>> min(_)
SemVer("1.6.9+b.112")
>>> _.patch
9
Exported classes:
* SemVer(collections.namedtuple())
Parses semantic versions and defines methods for them. Supports rich comparisons.
* SemSel(tuple)
Parses semantic version selector strings and defines methods for them.
* SelParseError(Exception)
An error among others raised when parsing a semantic version selector failed.
Other classes:
* SemComparator(object)
* SemSelAndChunk(list)
* SemSelOrChunk(list)
Functions/Variables/Constants:
none
Copyright (c) 2013 Zachary King, FichteFoll
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import re
import sys
from collections import namedtuple # Python >=2.6
__all__ = ('SemVer', 'SemSel', 'SelParseError')
if sys.version_info[0] == 3:
basestring = str
cmp = lambda a, b: (a > b) - (a < b)
# @functools.total_ordering would be nice here but was added in 2.7, __cmp__ is not Py3
class SemVer(namedtuple("_SemVer", 'major, minor, patch, prerelease, build')):
"""Semantic Version, consists of 3 to 5 components defining the version's adicity.
See http://semver.org/ (2.0.0-rc.1) for the standard mainly used for this implementation, few
changes have been made.
Information on this particular class and their instances:
- Immutable and hashable.
- Subclasses `collections.namedtuple`.
- Always `True` in boolean context.
- len() returns an int between 3 and 5; 4 when a pre-release is set and 5 when a build is
set. Note: Still returns 5 when build is set but not pre-release.
- Parts of the semantic version can be accessed by integer indexing, key (string) indexing,
slicing and getting an attribute. Returned slices are tuple. Leading '-' and '+' of
optional components are not stripped. Supported keys/attributes:
major, minor, patch, prerelease, build.
Examples:
s = SemVer("1.2.3-4.5+6")
s[2] == 3
s[:3] == (1, 2, 3)
s['build'] == '-4.5'
s.major == 1
Short information on semantic version structure:
Semantic versions consist of:
* a major component (numeric)
* a minor component (numeric)
* a patch component (numeric)
* a pre-release component [optional]
* a build component [optional]
The pre-release component is indicated by a hyphen '-' and followed by alphanumeric[1] sequences
separated by dots '.'. Sequences are compared numerically if applicable (both sequences of two
versions are numeric) or lexicographically. May also include hyphens. The existence of a
pre-release component lowers the actual version; the shorter pre-release component is considered
lower. An 'empty' pre-release component is considered to be the least version for this
major-minor-patch combination (e.g. "1.0.0-").
The build component may follow the optional pre-release component and is indicated by a plus '+'
followed by sequences, just as the pre-release component. Comparing works similarly. However the
existence of a build component raises the actual version and may also raise a pre-release. An
'empty' build component is considered to be the highest version for this
major-minor-patch-prerelease combination (e.g. "1.2.3+").
[1]: Regexp for a sequence: r'[0-9A-Za-z-]+'.
"""
# Static class variables
_base_regex = r'''(?x)
(?P<major>[0-9]+)
\.(?P<minor>[0-9]+)
\.(?P<patch>[0-9]+)
(?:\-(?P<prerelease>(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?))?
(?:\+(?P<build>(?:[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?))?'''
_search_regex = re.compile(_base_regex)
_match_regex = re.compile('^%s$' % _base_regex) # required because of $ anchor
# "Constructor"
def __new__(cls, *args, **kwargs):
"""There are two different constructor styles that are allowed:
- Option 1 allows specification of a semantic version as a string and the option to "clean"
the string before parsing it.
- Option 2 allows specification of each component separately as one parameter.
Note that all the parameters specified in the following sections can be passed either as
positional or as named parameters while considering the usual Python rules for this. As
such, `SemVer(1, 2, minor=1)` will result in an exception and not in `SemVer("1.1.2")`.
Option 1:
Constructor examples:
SemVer("1.0.1")
SemVer("this version 1.0.1-pre.1 here", True)
SemVer(ver="0.0.9-pre-alpha+34", clean=False)
Parameters:
* ver (str)
The string containing the version.
* clean = `False` (bool; optional)
If this is true in boolean context, `SemVer.clean(ver)` is called before
parsing.
Option 2:
Constructor examples:
SemVer(1, 0, 1)
SemVer(1, '0', prerelease='pre-alpha', patch=1, build=34)
SemVer(**dict(minor=2, major=1, patch=3))
Parameters:
* major (int, str, float ...)
* minor (...)
* patch (...)
Major to patch components must be an integer or convertable to an int (e.g. a
string or another number type).
* prerelease = `None` (str, int, float ...; optional)
* build = `None` (...; optional)
Pre-release and build components should be a string (or number) type.
Will be passed to `str()` if not already a string but the final string must
match '^[0-9A-Za-z.-]*$'
Raises:
* TypeError
Invalid parameter type(s) or combination (e.g. option 1 and 2).
* ValueError
Invalid semantic version or option 2 parameters unconvertable.
"""
ver, clean, comps = None, False, None
kw, l = kwargs.copy(), len(args) + len(kwargs)
def inv():
raise TypeError("Invalid parameter combination: args=%s; kwargs=%s" % (args, kwargs))
# Do validation and parse the parameters
if l == 0 or l > 5:
raise TypeError("SemVer accepts at least 1 and at most 5 arguments (%d given)" % l)
elif l < 3:
if len(args) == 2:
ver, clean = args
else:
ver = args[0] if args else kw.pop('ver', None)
clean = kw.pop('clean', clean)
if kw:
inv()
else:
comps = list(args) + [kw.pop(cls._fields[k], None) for k in range(len(args), 5)]
if kw or any(comps[i] is None for i in range(3)):
inv()
typecheck = (int,) * 3 + (basestring,) * 2
for i, (v, t) in enumerate(zip(comps, typecheck)):
if v is None:
continue
elif not isinstance(v, t):
try:
if i < 3:
v = typecheck[i](v)
else: # The real `basestring` can not be instatiated (Py2)
v = str(v)
except ValueError as e:
# Modify the exception message. I can't believe this actually works
e.args = ("Parameter #%d must be of type %s or convertable"
% (i, t.__name__),)
raise
else:
comps[i] = v
if t is basestring and not re.match(r"^[0-9A-Za-z.-]*$", v):
raise ValueError("Build and pre-release strings must match '^[0-9A-Za-z.-]*$'")
# Final adjustments
if not comps:
if ver is None or clean is None:
inv()
ver = clean and cls.clean(ver) or ver
comps = cls._parse(ver)
# Create the obj
return super(SemVer, cls).__new__(cls, *comps)
# Magic methods
def __str__(self):
return ('.'.join(map(str, self[:3]))
+ ('-' + self.prerelease if self.prerelease is not None else '')
+ ('+' + self.build if self.build is not None else ''))
def __repr__(self):
# Use the shortest representation - what would you prefer?
return 'SemVer("%s")' % str(self)
# return 'SemVer(%s)' % ', '.join('%s=%r' % (k, getattr(self, k)) for k in self._fields)
def __len__(self):
return 3 + (self.build is not None and 2 or self.prerelease is not None)
# Magic rich comparing methods
def __gt__(self, other):
return self._compare(other) == 1 if isinstance(other, SemVer) else NotImplemented
def __eq__(self, other):
return self._compare(other) == 0 if isinstance(other, SemVer) else NotImplemented
def __lt__(self, other):
return not (self > other or self == other)
def __ge__(self, other):
return not (self < other)
def __le__(self, other):
return not (self > other)
def __ne__(self, other):
return not (self == other)
# Utility (class-)methods
def satisfies(self, sel):
"""Alias for `bool(sel.matches(self))` or `bool(SemSel(sel).matches(self))`.
See `SemSel.__init__()` and `SemSel.matches(*vers)` for possible exceptions.
Returns:
* bool: `True` if the version matches the passed selector, `False` otherwise.
"""
if not isinstance(sel, SemSel):
sel = SemSel(sel) # just "re-raise" exceptions
return bool(sel.matches(self))
@classmethod
def valid(cls, ver):
"""Check if `ver` is a valid semantic version. Classmethod.
Parameters:
* ver (str)
The string that should be stripped.
Raises:
* TypeError
Invalid parameter type.
Returns:
* bool: `True` if it is valid, `False` otherwise.
"""
if not isinstance(ver, basestring):
raise TypeError("%r is not a string" % ver)
if cls._match_regex.match(ver):
return True
else:
return False
@classmethod
def clean(cls, vers):
"""Remove everything before and after a valid version string. Classmethod.
Parameters:
* vers (str)
The string that should be stripped.
Raises:
* TypeError
Invalid parameter type.
Returns:
* str: The stripped version string. Only the first version is matched.
* None: No version found in the string.
"""
if not isinstance(vers, basestring):
raise TypeError("%r is not a string" % vers)
m = cls._search_regex.search(vers)
if m:
return vers[m.start():m.end()]
else:
return None
# Private (class-)methods
@classmethod
def _parse(cls, ver):
"""Private. Do not touch. Classmethod.
"""
if not isinstance(ver, basestring):
raise TypeError("%r is not a string" % ver)
match = cls._match_regex.match(ver)
if match is None:
raise ValueError("'%s' is not a valid SemVer string" % ver)
g = list(match.groups())
for i in range(3):
g[i] = int(g[i])
return g # Will be passed as namedtuple(...)(*g)
def _compare(self, other):
"""Private. Do not touch.
self > other: 1
self = other: 0
self < other: -1
"""
# Shorthand lambdas
cp_len = lambda t, i=0: cmp(len(t[i]), len(t[not i]))
for i, (x1, x2) in enumerate(zip(self, other)):
if i > 2:
if x1 is None and x2 is None:
continue
# self is greater when other has a prerelease but self doesn't
# self is less when other has a build but self doesn't
if x1 is None or x2 is None:
return int(2 * (i - 3.5)) * (1 - 2 * (x1 is None))
# self is less when other's build is empty
if i == 4 and (not x1 or not x2) and x1 != x2:
return 1 - 2 * bool(x1)
# Split by '.' and use numeric comp or lexicographical order
t2 = [x1.split('.'), x2.split('.')]
for y1, y2 in zip(*t2):
if y1.isdigit() and y2.isdigit():
y1 = int(y1)
y2 = int(y2)
if y1 > y2:
return 1
elif y1 < y2:
return -1
# The "longer" sub-version is greater
d = cp_len(t2)
if d:
return d
else:
if x1 > x2:
return 1
elif x1 < x2:
return -1
# The versions equal
return 0
class SemComparator(object):
"""Holds a SemVer object and a comparing operator and can match these against a given version.
Constructor: SemComparator('<=', SemVer("1.2.3"))
Methods:
* matches(ver)
"""
# Private properties
_ops = {
'>=': '__ge__',
'<=': '__le__',
'>': '__gt__',
'<': '__lt__',
'=': '__eq__',
'!=': '__ne__'
}
_ops_satisfy = ('~', '!')
# Constructor
def __init__(self, op, ver):
"""Constructor examples:
SemComparator('<=', SemVer("1.2.3"))
SemComparator('!=', SemVer("2.3.4"))
Parameters:
* op (str, False, None)
One of [>=, <=, >, <, =, !=, !, ~] or evaluates to `False` which defaults to '~'.
'~' means a "satisfy" operation where pre-releases and builds are ignored.
'!' is a negative "~".
* ver (SemVer)
Holds the version to compare with.
Raises:
* ValueError
Invalid `op` parameter.
* TypeError
Invalid `ver` parameter.
"""
super(SemComparator, self).__init__()
if op and op not in self._ops_satisfy and op not in self._ops:
raise ValueError("Invalid value for `op` parameter.")
if not isinstance(ver, SemVer):
raise TypeError("`ver` parameter is not instance of SemVer.")
# Default to '~' for versions with no build or pre-release
op = op or '~'
# Fallback to '=' and '!=' if len > 3
if len(ver) != 3:
if op == '~':
op = '='
if op == '!':
op = '!='
self.op = op
self.ver = ver
# Magic methods
def __str__(self):
return (self.op or "") + str(self.ver)
# Utility methods
def matches(self, ver):
"""Match the internal version (constructor) against `ver`.
Parameters:
* ver (SemVer)
Raises:
* TypeError
Could not compare `ver` against the version passed in the constructor with the
passed operator.
Returns:
* bool
`True` if the version matched the specified operator and internal version, `False`
otherwise.
"""
if self.op in self._ops_satisfy:
# Compare only the first three parts (which are tuples) and directly
return bool((self.ver[:3] == ver[:3]) + (self.op == '!') * -1)
ret = getattr(ver, self._ops[self.op])(self.ver)
if ret == NotImplemented:
raise TypeError("Unable to compare %r with operator '%s'" % (ver, self.op))
return ret
class SemSelAndChunk(list):
"""Extends list and defines a few methods used for matching versions.
New elements should be added by calling `.add_child(op, ver)` which creates a SemComparator
instance and adds that to itself.
Methods:
* matches(ver)
* add_child(op, ver)
"""
# Magic methods
def __str__(self):
return ' '.join(map(str, self))
# Utitlity methods
def matches(self, ver):
"""Match all of the added children against `ver`.
Parameters:
* ver (SemVer)
Raises:
* TypeError
Invalid `ver` parameter.
Returns:
* bool:
`True` if *all* of the SemComparator children match `ver`, `False` otherwise.
"""
if not isinstance(ver, SemVer):
raise TypeError("`ver` parameter is not instance of SemVer.")
return all(cp.matches(ver) for cp in self)
def add_child(self, op, ver):
"""Create a SemComparator instance with the given parameters and appends that to self.
Parameters:
* op (str)
* ver (SemVer)
Both parameters are forwarded to `SemComparator.__init__`, see there for a more detailed
description.
Raises:
Exceptions raised by `SemComparator.__init__`.
"""
self.append(SemComparator(op, SemVer(ver)))
class SemSelOrChunk(list):
"""Extends list and defines a few methods used for matching versions.
New elements should be added by calling `.new_child()` which returns a SemSelAndChunk
instance.
Methods:
* matches(ver)
* new_child()
"""
# Magic methods
def __str__(self):
return ' || '.join(map(str, self))
# Utility methods
def matches(self, ver):
"""Match all of the added children against `ver`.
Parameters:
* ver (SemVer)
Raises:
* TypeError
Invalid `ver` parameter.
Returns:
* bool
`True` if *any* of the SemSelAndChunk children matches `ver`.
`False` otherwise.
"""
if not isinstance(ver, SemVer):
raise TypeError("`ver` parameter is not instance of SemVer.")
return any(ch.matches(ver) for ch in self)
def new_child(self):
"""Creates a new SemSelAndChunk instance, appends it to self and returns it.
Returns:
* SemSelAndChunk: An empty instance.
"""
ch = SemSelAndChunk()
self.append(ch)
return ch
class SelParseError(Exception):
"""An Exception raised when parsing a semantic selector failed.
"""
pass
# Subclass `tuple` because this is a somewhat simple method to make this immutable
class SemSel(tuple):
"""A Semantic Version Selector, holds a selector and can match it against semantic versions.
Information on this particular class and their instances:
- Immutable but not hashable because the content within might have changed.
- Subclasses `tuple` but does not behave like one.
- Always `True` in boolean context.
- len() returns the number of containing *and chunks* (see below).
- Iterable, iterates over containing *and chunks*.
When talking about "versions" it refers to a semantic version (SemVer). For information on how
versions compare to one another, see SemVer's doc string.
List for **comparators**:
"1.0.0" matches the version 1.0.0 and all its pre-release and build variants
"!1.0.0" matches any version that is not 1.0.0 or any of its variants
"=1.0.0" matches only the version 1.0.0
"!=1.0.0" matches any version that is not 1.0.0
">=1.0.0" matches versions greater than or equal 1.0.0
"<1.0.0" matches versions smaller than 1.0.0
"1.0.0 - 1.0.3" matches versions greater than or equal 1.0.0 thru 1.0.3
"~1.0" matches versions greater than or equal 1.0.0 thru 1.0.9999 (and more)
"~1", "1.x", "1.*" match versions greater than or equal 1.0.0 thru 1.9999.9999 (and more)
"~1.1.2" matches versions greater than or equal 1.1.2 thru 1.1.9999 (and more)
"~1.1.2+any" matches versions greater than or equal 1.1.2+any thru 1.1.9999 (and more)
"*", "~", "~x" match any version
Multiple comparators can be combined by using ' ' spaces and every comparator must match to make
the **and chunk** match a version.
Multiple and chunks can be combined to **or chunks** using ' || ' and match if any of the and
chunks split by these matches.
A complete example would look like:
~1 || 0.0.3 || <0.0.2 >0.0.1+b.1337 || 2.0.x || 2.1.0 - 2.1.0+b.12 !=2.1.0+b.9
Methods:
* matches(*vers)
"""
# Private properties
_fuzzy_regex = re.compile(r'''(?x)^
(?P<op>[<>]=?|~>?=?)?
(?:(?P<major>\d+)
(?:\.(?P<minor>\d+)
(?:\.(?P<patch>\d+)
(?P<other>[-+][a-zA-Z0-9-+.]*)?
)?
)?
)?$''')
_xrange_regex = re.compile(r'''(?x)^
(?P<op>[<>]=?|~>?=?)?
(?:(?P<major>\d+|[xX*])
(?:\.(?P<minor>\d+|[xX*])
(?:\.(?P<patch>\d+|[xX*]))?
)?
)
(?P<other>.*)$''')
_split_op_regex = re.compile(r'^(?P<op>=|[<>!]=?)?(?P<ver>.*)$')
# "Constructor"
def __new__(cls, sel):
"""Constructor examples:
SemSel(">1.0.0")
SemSel("~1.2.9 !=1.2.12")
Parameters:
* sel (str)
A version selector string.
Raises:
* TypeError
`sel` parameter is not a string.
* ValueError
A version in the selector could not be matched as a SemVer.
* SemParseError
The version selector's syntax is unparsable; invalid ranges (fuzzy, xrange or
explicit range) or invalid '||'
"""
chunk = cls._parse(sel)
return super(SemSel, cls).__new__(cls, (chunk,))
# Magic methods
def __str__(self):
return str(self._chunk)
def __repr__(self):
return 'SemSel("%s")' % self._chunk
def __len__(self):
# What would you expect?
return len(self._chunk)
def __iter__(self):
return iter(self._chunk)
# Read-only (private) attributes
@property
def _chunk(self):
return self[0]
# Utility methods
def matches(self, *vers):
"""Match the selector against a selection of versions.
Parameters:
* *vers (str, SemVer)
Versions can be passed as strings and SemVer objects will be created with them.
May also be a mixed list.
Raises:
* TypeError
A version is not an instance of str (basestring) or SemVer.
* ValueError
A string version could not be parsed as a SemVer.
Returns:
* list
A list with all the versions that matched, may be empty. Use `max()` to determine
the highest matching version, or `min()` for the lowest.
"""
ret = []
for v in vers:
if isinstance(v, str):
t = self._chunk.matches(SemVer(v))
elif isinstance(v, SemVer):
t = self._chunk.matches(v)
else:
raise TypeError("Invalid parameter type '%s': %s" % (v, type(v)))
if t:
ret.append(v)
return ret
# Private methods
@classmethod
def _parse(cls, sel):
"""Private. Do not touch.
1. split by whitespace into tokens
a. start new and_chunk on ' || '
b. parse " - " ranges
c. replace "xX*" ranges with "~" equivalent
d. parse "~" ranges
e. parse unmatched token as comparator
~. append to current and_chunk
2. return SemSelOrChunk
Raises TypeError, ValueError or SelParseError.
"""
if not isinstance(sel, basestring):
raise TypeError("Selector must be a string")
if not sel:
raise ValueError("String must not be empty")
# Split selector by spaces and crawl the tokens
tokens = sel.split()
i = -1
or_chunk = SemSelOrChunk()
and_chunk = or_chunk.new_child()
while i + 1 < len(tokens):
i += 1
t = tokens[i]
# Replace x ranges with ~ selector
m = cls._xrange_regex.match(t)
m = m and m.groups('')
if m and any(not x.isdigit() for x in m[1:4]) and not m[0].startswith('>'):
# (do not match '>1.0' or '>*')
if m[4]:
raise SelParseError("XRanges do not allow pre-release or build components")
# Only use digit parts and fail if digit found after non-digit
mm, xran = [], False
for x in m[1:4]:
if x.isdigit():
if xran:
raise SelParseError("Invalid fuzzy range or XRange '%s'" % tokens[i])
mm.append(x)
else:
xran = True
t = m[0] + '.'.join(mm) # x for x in m[1:4] if x.isdigit())
# Append "~" if not already present
if not t.startswith('~'):
t = '~' + t
# switch t:
if t == '||':
if i == 0 or tokens[i - 1] == '||' or i + 1 == len(tokens):
raise SelParseError("OR range must not be empty")
# Start a new and_chunk
and_chunk = or_chunk.new_child()
elif t == '-':
# ' - ' range
i += 1
invalid = False
try:
# If these result in exceptions, you know you're doing it wrong
t = tokens[i]
c = and_chunk[-1]
except:
raise SelParseError("Invalid ' - ' range position")
# If there is an op in front of one of the bound versions
invalid = (c.op not in ('=', '~')
or cls._split_op_regex.match(t).group(1) not in (None, '='))
if invalid:
raise SelParseError("Invalid ' - ' range '%s - %s'"
% (tokens[i - 2], tokens[i]))
c.op = ">="
and_chunk.add_child('<=', t)
elif t == '':
# Multiple spaces
pass
elif t.startswith('~'):
m = cls._fuzzy_regex.match(t)
if not m:
raise SelParseError("Invalid fuzzy range or XRange '%s'" % tokens[i])
mm, m = m.groups('')[1:4], m.groupdict('') # mm: major to patch
# Minimum requirement
min_ver = ('.'.join(x or '0' for x in mm) + '-'
if not m['other']
else cls._split_op_regex(t[1:]).group('ver'))
and_chunk.add_child('>=', min_ver)
if m['major']:
# Increase version before none (or second to last if '~1.2.3')
e = [0, 0, 0]
for j, d in enumerate(mm):
if not d or j == len(mm) - 1:
e[j - 1] = e[j - 1] + 1
break
e[j] = int(d)
and_chunk.add_child('<', '.'.join(str(x) for x in e) + '-')
# else: just plain '~' or '*', or '~>X' which are already handled
else:
# A normal comparator
m = cls._split_op_regex.match(t).groupdict() # this regex can't fail
and_chunk.add_child(**m)
# Finally return the or_chunk
return or_chunk