From f564e42e60191e7f74911bbf035d8a547e307391 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Mon, 1 May 2023 21:35:58 +0300 Subject: [PATCH] Cleanup dependencies and fix #94 (#95) Upgrade Parsimonious to `v0.10.0+39b3d71ee827ba6a19a7226fddfe6b6e51535794`; fix https://github.com/OpenCyphal/pydsdl/issues/92. Compare the bundled version of Parsimonious against its latest release, v0.10.0: https://github.com/erikrose/parsimonious/compare/0.10.0...39b3d71ee827ba6a19a7226fddfe6b6e51535794 Remove dependency on `six.py` Fix https://github.com/OpenCyphal/pydsdl/issues/94 and add a new error class -- `DataTypeCollisionError`; remove `MultipleDefinitionsUnderSameVersionError`. This does not affect the API compatibility because the specific error classes are private. Bump the minor version number. --- docs/index.rst | 23 - docs/pages/installation.rst | 1 - pydsdl/__init__.py | 2 +- pydsdl/_namespace.py | 37 +- pydsdl/_test.py | 23 +- pydsdl/third_party/parsimonious/exceptions.py | 49 +- .../third_party/parsimonious/expressions.py | 244 ++--- pydsdl/third_party/parsimonious/grammar.py | 135 ++- pydsdl/third_party/parsimonious/nodes.py | 20 +- pydsdl/third_party/parsimonious/utils.py | 11 +- pydsdl/third_party/six.py | 998 ------------------ setup.cfg | 3 - update_third_party_subtrees.sh | 5 +- 13 files changed, 292 insertions(+), 1259 deletions(-) delete mode 100644 pydsdl/third_party/six.py diff --git a/docs/index.rst b/docs/index.rst index 37cb6f0..0d15657 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -63,26 +63,3 @@ 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. - -Six -^^^ - -Copyright (c) 2010-2020 Benjamin Peterson - -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. diff --git a/docs/pages/installation.rst b/docs/pages/installation.rst index 7a90db7..42614ac 100644 --- a/docs/pages/installation.rst +++ b/docs/pages/installation.rst @@ -22,6 +22,5 @@ The library is bundled with the following third-party software libraries (by virtue of being bundled, they are not listed as dependencies and need not be installed by the user): * `Parsimonious `_ by Erik Rose, MIT license. -* `Six `_ by Benjamin Peterson, MIT license; needed for Parsimonious. Please refer to the projects' homepages for more information, including the legal information on licensing. diff --git a/pydsdl/__init__.py b/pydsdl/__init__.py index c0422a7..5662cf1 100644 --- a/pydsdl/__init__.py +++ b/pydsdl/__init__.py @@ -7,7 +7,7 @@ import sys as _sys from pathlib import Path as _Path -__version__ = "1.19.0" +__version__ = "1.20.0" __version_info__ = tuple(map(int, __version__.split(".")[:3])) __license__ = "MIT" __author__ = "OpenCyphal" diff --git a/pydsdl/_namespace.py b/pydsdl/_namespace.py index 600408a..82bb84e 100644 --- a/pydsdl/_namespace.py +++ b/pydsdl/_namespace.py @@ -20,7 +20,13 @@ class RootNamespaceNameCollisionError(_error.InvalidDefinitionError): """ -class DataTypeNameCollisionError(_error.InvalidDefinitionError): +class DataTypeCollisionError(_error.InvalidDefinitionError): + """ + Raised when there are conflicting data type definitions. + """ + + +class DataTypeNameCollisionError(DataTypeCollisionError): """ Raised when there are conflicting data type names. """ @@ -39,16 +45,6 @@ class FixedPortIDCollisionError(_error.InvalidDefinitionError): """ -class MultipleDefinitionsUnderSameVersionError(_error.InvalidDefinitionError): - """ - For example:: - - Type.1.0.dsdl - 2800.Type.1.0.dsdl - 2801.Type.1.0.dsdl - """ - - class VersionsOfDifferentKindError(_error.InvalidDefinitionError): """ Definitions that share the same name but are of different kinds. @@ -166,7 +162,7 @@ def read_namespace( lookup_dsdl_definitions += _construct_dsdl_definitions_from_namespace(ld) # Check for collisions against the lookup definitions also. - _ensure_no_name_collisions(target_dsdl_definitions, lookup_dsdl_definitions) + _ensure_no_collisions(target_dsdl_definitions, lookup_dsdl_definitions) _logger.debug("Lookup DSDL definitions are listed below:") for x in lookup_dsdl_definitions: @@ -245,7 +241,7 @@ def handler(line_number: int, text: str) -> None: return types -def _ensure_no_name_collisions( +def _ensure_no_collisions( target_definitions: List[_dsdl_definition.DSDLDefinition], lookup_definitions: List[_dsdl_definition.DSDLDefinition], ) -> None: @@ -275,6 +271,12 @@ def _ensure_no_name_collisions( raise DataTypeNameCollisionError( "This type conflicts with the namespace of %s" % lu.file_path, path=tg.file_path ) + if ( + tg_full_name_period == lu_full_name_period + and tg.version == lu.version + and not tg.file_path.samefile(lu.file_path) + ): # https://github.com/OpenCyphal/pydsdl/issues/94 + raise DataTypeCollisionError("This type is redefined in %s" % lu.file_path, path=tg.file_path) def _ensure_no_fixed_port_id_collisions(types: List[_serializable.CompositeType]) -> None: @@ -320,14 +322,9 @@ def _ensure_minor_version_compatibility_pairwise( a: _serializable.CompositeType, b: _serializable.CompositeType ) -> None: assert a is not b - assert a.version.major == b.version.major assert a.full_name == b.full_name - - # Version collision - if a.version.minor == b.version.minor: - raise MultipleDefinitionsUnderSameVersionError( - "This definition shares its version number with %s" % b.source_file_path, path=a.source_file_path - ) + assert a.version.major == b.version.major + assert a.version.minor != b.version.minor # This is the whole point of this function. # Must be of the same kind: both messages or both services if isinstance(a, _serializable.ServiceType) != isinstance(b, _serializable.ServiceType): diff --git a/pydsdl/_test.py b/pydsdl/_test.py index f81f735..3e4048f 100644 --- a/pydsdl/_test.py +++ b/pydsdl/_test.py @@ -1262,7 +1262,7 @@ def _unittest_parse_namespace_versioning(wrkspc: Workspace) -> None: ), ) - with raises(_namespace.MultipleDefinitionsUnderSameVersionError): + with raises(_namespace.DataTypeCollisionError): _namespace.read_namespace((wrkspc.directory / "ns"), []) wrkspc.drop("ns/Spartans.30.2.dsdl") @@ -1604,6 +1604,27 @@ def _unittest_parse_namespace_versioning(wrkspc: Workspace) -> None: wrkspc.drop("ns/Consistency*") +def _unittest_issue94(wrkspc: Workspace) -> None: + from pytest import raises + + wrkspc.new("outer_a/ns/Foo.1.0.dsdl", "@sealed") + wrkspc.new("outer_b/ns/Foo.1.0.dsdl", "@sealed") # Conflict! + wrkspc.new("outer_a/ns/Bar.1.0.dsdl", "Foo.1.0 fo\n@sealed") # Which Foo.1.0? + + with raises(_namespace.DataTypeCollisionError): + _namespace.read_namespace( + wrkspc.directory / "outer_a" / "ns", + [wrkspc.directory / "outer_b" / "ns"], + ) + + wrkspc.drop("outer_b/ns/Foo.1.0.dsdl") # Clear the conflict. + defs = _namespace.read_namespace( + wrkspc.directory / "outer_a" / "ns", + [wrkspc.directory / "outer_b" / "ns"], + ) + assert len(defs) == 2 + + def _unittest_parse_namespace_faults() -> None: from pytest import raises diff --git a/pydsdl/third_party/parsimonious/exceptions.py b/pydsdl/third_party/parsimonious/exceptions.py index ee14623..b9e05bc 100644 --- a/pydsdl/third_party/parsimonious/exceptions.py +++ b/pydsdl/third_party/parsimonious/exceptions.py @@ -1,10 +1,14 @@ -from six import text_type, python_2_unicode_compatible +from textwrap import dedent from parsimonious.utils import StrAndRepr -@python_2_unicode_compatible -class ParseError(StrAndRepr, Exception): +class ParsimoniousError(Exception): + """A base exception class to allow library users to catch any Parsimonious error.""" + pass + + +class ParseError(StrAndRepr, ParsimoniousError): """A call to ``Expression.parse()`` or ``match()`` didn't match.""" def __init__(self, text, pos=-1, expr=None): @@ -16,9 +20,9 @@ def __init__(self, text, pos=-1, expr=None): self.expr = expr def __str__(self): - rule_name = ((u"'%s'" % self.expr.name) if self.expr.name else - text_type(self.expr)) - return u"Rule %s didn't match at '%s' (line %s, column %s)." % ( + rule_name = (("'%s'" % self.expr.name) if self.expr.name else + str(self.expr)) + return "Rule %s didn't match at '%s' (line %s, column %s)." % ( rule_name, self.text[self.pos:self.pos + 20], self.line(), @@ -32,31 +36,47 @@ def line(self): match.""" # This is a method rather than a property in case we ever wanted to # pass in which line endings we want to use. - return self.text.count('\n', 0, self.pos) + 1 + if isinstance(self.text, list): # TokenGrammar + return None + else: + return self.text.count('\n', 0, self.pos) + 1 def column(self): """Return the 1-based column where the expression ceased to match.""" # We choose 1-based because that's what Python does with SyntaxErrors. try: return self.pos - self.text.rindex('\n', 0, self.pos) - except ValueError: + except (ValueError, AttributeError): return self.pos + 1 -@python_2_unicode_compatible +class LeftRecursionError(ParseError): + def __str__(self): + rule_name = self.expr.name if self.expr.name else str(self.expr) + window = self.text[self.pos:self.pos + 20] + return dedent(f""" + Left recursion in rule {rule_name!r} at {window!r} (line {self.line()}, column {self.column()}). + + Parsimonious is a packrat parser, so it can't handle left recursion. + See https://en.wikipedia.org/wiki/Parsing_expression_grammar#Indirect_left_recursion + for how to rewrite your grammar into a rule that does not use left-recursion. + """ + ).strip() + + class IncompleteParseError(ParseError): """A call to ``parse()`` matched a whole Expression but did not consume the entire text.""" def __str__(self): - return u"Rule '%s' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with '%s' (line %s, column %s)." % ( + return "Rule '%s' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with '%s' (line %s, column %s)." % ( self.expr.name, self.text[self.pos:self.pos + 20], self.line(), self.column()) -class VisitationError(Exception): +class VisitationError(ParsimoniousError): """Something went wrong while traversing a parse tree. This exception exists to augment an underlying exception with information @@ -76,7 +96,7 @@ def __init__(self, exc, exc_class, node): """ self.original_class = exc_class - super(VisitationError, self).__init__( + super().__init__( '%s: %s\n\n' 'Parse tree:\n' '%s' % @@ -85,7 +105,7 @@ def __init__(self, exc, exc_class, node): node.prettily(error=node))) -class BadGrammar(StrAndRepr, Exception): +class BadGrammar(StrAndRepr, ParsimoniousError): """Something was wrong with the definition of a grammar. Note that a ParseError might be raised instead if the error is in the @@ -94,7 +114,6 @@ class BadGrammar(StrAndRepr, Exception): """ -@python_2_unicode_compatible class UndefinedLabel(BadGrammar): """A rule referenced in a grammar was never defined. @@ -106,4 +125,4 @@ def __init__(self, label): self.label = label def __str__(self): - return u'The label "%s" was never defined.' % self.label + return 'The label "%s" was never defined.' % self.label diff --git a/pydsdl/third_party/parsimonious/expressions.py b/pydsdl/third_party/parsimonious/expressions.py index 192eb17..a1cf40b 100644 --- a/pydsdl/third_party/parsimonious/expressions.py +++ b/pydsdl/third_party/parsimonious/expressions.py @@ -6,17 +6,21 @@ # TODO: Make sure all symbol refs are local--not class lookups or # anything--for speed. And kill all the dots. -from inspect import getfullargspec -import re - -from six import integer_types, python_2_unicode_compatible -from six.moves import range - -from parsimonious.exceptions import ParseError, IncompleteParseError +from collections import defaultdict +from inspect import getfullargspec, isfunction, ismethod, ismethoddescriptor +try: + import regex as re +except ImportError: + import re # Fallback as per https://github.com/erikrose/parsimonious/issues/231 + +from parsimonious.exceptions import ParseError, IncompleteParseError, LeftRecursionError from parsimonious.nodes import Node, RegexNode from parsimonious.utils import StrAndRepr -MARKER = object() + +def is_callable(value): + criteria = [isfunction, ismethod, ismethoddescriptor] + return any([criterion(value) for criterion in criteria]) def expression(callable, rule_name, grammar): @@ -57,7 +61,16 @@ def foo(text, pos, cache, error, grammar): part of, to make delegating to other rules possible """ + + # Resolve unbound methods; allows grammars to use @staticmethod custom rules + # https://stackoverflow.com/questions/41921255/staticmethod-object-is-not-callable + if ismethoddescriptor(callable) and hasattr(callable, '__func__'): + callable = callable.__func__ + num_args = len(getfullargspec(callable).args) + if ismethod(callable): + # do not count the first argument (typically 'self') for methods + num_args -= 1 if num_args == 2: is_simple = True elif num_args == 5: @@ -71,7 +84,7 @@ def _uncached_match(self, text, pos, cache, error): result = (callable(text, pos) if is_simple else callable(text, pos, cache, error, grammar)) - if isinstance(result, integer_types): + if isinstance(result, int): end, children = result, None elif isinstance(result, tuple): end, children = result @@ -86,7 +99,9 @@ def _as_rhs(self): return AdHocExpression(name=rule_name) -@python_2_unicode_compatible +IN_PROGRESS = object() + + class Expression(StrAndRepr): """A thing that can be matched against a piece of text""" @@ -104,11 +119,20 @@ def __hash__(self): return hash(self.identity_tuple) def __eq__(self, other): - return isinstance(other, self.__class__) and self.identity_tuple == other.identity_tuple + return self._eq_check_cycles(other, set()) def __ne__(self, other): return not (self == other) + def _eq_check_cycles(self, other, checked): + # keep a set of all pairs that are already checked, so we won't fall into infinite recursions. + checked.add((id(self), id(other))) + return other.__class__ is self.__class__ and self.identity_tuple == other.identity_tuple + + def resolve_refs(self, rule_map): + # Nothing to do on the base expression. + return self + def parse(self, text, pos=0): """Return a parse tree of ``text``. @@ -132,7 +156,7 @@ def match(self, text, pos=0): """ error = ParseError(text) - node = self.match_core(text, pos, {}, error) + node = self.match_core(text, pos, defaultdict(dict), error) if node is None: raise error return node @@ -157,8 +181,7 @@ def match_core(self, text, pos, cache, error): """ # TODO: Optimize. Probably a hot spot. # - # Is there a way of looking up cached stuff that's faster than hashing - # this id-pos pair? + # Is there a faster way of looking up cached stuff? # # If this is slow, think about the array module. It might (or might # not!) use more RAM, but it'll likely be faster than hashing things @@ -169,13 +192,15 @@ def match_core(self, text, pos, cache, error): # only the results of entire rules, not subexpressions (probably a # horrible idea for rules that need to backtrack internally a lot). (2) # Age stuff out of the cache somehow. LRU? (3) Cuts. - expr_id = id(self) - node = cache.get((expr_id, pos), MARKER) # TODO: Change to setdefault to prevent infinite recursion in left-recursive rules. - if node is MARKER: - node = cache[(expr_id, pos)] = self._uncached_match(text, - pos, - cache, - error) + expr_cache = cache[id(self)] + if pos in expr_cache: + node = expr_cache[pos] + else: + # TODO: Set default value to prevent infinite recursion in left-recursive rules. + expr_cache[pos] = IN_PROGRESS # Mark as in progress + node = expr_cache[pos] = self._uncached_match(text, pos, cache, error) + if node is IN_PROGRESS: + raise LeftRecursionError(text, pos=-1, expr=self) # Record progress for error reporting: if node is None and pos >= error.pos and ( @@ -190,7 +215,7 @@ def match_core(self, text, pos, cache, error): return node def __str__(self): - return u'<%s %s>' % ( + return '<%s %s>' % ( self.__class__.__name__, self.as_rule()) @@ -204,7 +229,7 @@ def as_rule(self): if rhs.startswith('(') and rhs.endswith(')'): rhs = rhs[1:-1] - return (u'%s = %s' % (self.name, rhs)) if self.name else rhs + return ('%s = %s' % (self.name, rhs)) if self.name else rhs def _unicode_members(self): """Return an iterable of my unicode-represented children, stopping @@ -230,7 +255,7 @@ class Literal(Expression): __slots__ = ['literal'] def __init__(self, literal, name=''): - super(Literal, self).__init__(name) + super().__init__(name) self.literal = literal self.identity_tuple = (name, literal) @@ -239,8 +264,7 @@ def _uncached_match(self, text, pos, cache, error): return Node(self, text, pos, pos + len(self.literal)) def _as_rhs(self): - # TODO: Get backslash escaping right. - return '"%s"' % self.literal + return repr(self.literal) class TokenMatcher(Literal): @@ -264,14 +288,15 @@ class Regex(Expression): __slots__ = ['re'] def __init__(self, pattern, name='', ignore_case=False, locale=False, - multiline=False, dot_all=False, unicode=False, verbose=False): - super(Regex, self).__init__(name) + multiline=False, dot_all=False, unicode=False, verbose=False, ascii=False): + super().__init__(name) self.re = re.compile(pattern, (ignore_case and re.I) | (locale and re.L) | (multiline and re.M) | (dot_all and re.S) | (unicode and re.U) | - (verbose and re.X)) + (verbose and re.X) | + (ascii and re.A)) self.identity_tuple = (self.name, self.re) def _uncached_match(self, text, pos, cache, error): @@ -285,13 +310,12 @@ def _uncached_match(self, text, pos, cache, error): def _regex_flags_from_bits(self, bits): """Return the textual equivalent of numerically encoded regex flags.""" - flags = 'ilmsux' + flags = 'ilmsuxa' return ''.join(flags[i - 1] if (1 << i) & bits else '' for i in range(1, len(flags) + 1)) def _as_rhs(self): - # TODO: Get backslash escaping right. - return '~"%s"%s' % (self.re.pattern, - self._regex_flags_from_bits(self.re.flags)) + return '~{!r}{}'.format(self.re.pattern, + self._regex_flags_from_bits(self.re.flags)) class Compound(Expression): @@ -301,21 +325,26 @@ class Compound(Expression): def __init__(self, *members, **kwargs): """``members`` is a sequence of expressions.""" - super(Compound, self).__init__(kwargs.get('name', '')) + super().__init__(kwargs.get('name', '')) self.members = members + def resolve_refs(self, rule_map): + self.members = tuple(m.resolve_refs(rule_map) for m in self.members) + return self + + def _eq_check_cycles(self, other, checked): + return ( + super()._eq_check_cycles(other, checked) and + len(self.members) == len(other.members) and + all(m._eq_check_cycles(mo, checked) for m, mo in zip(self.members, other.members) if (id(m), id(mo)) not in checked) + ) + def __hash__(self): # Note we leave members out of the hash computation, since compounds can get added to # sets, then have their members mutated. See RuleVisitor._resolve_refs. # Equality should still work, but we want the rules to go into the correct hash bucket. return hash((self.__class__, self.name)) - def __eq__(self, other): - return ( - isinstance(other, self.__class__) and - self.name == other.name and - self.members == other.members) - class Sequence(Compound): """A series of expressions that must match contiguous, ordered pieces of @@ -327,7 +356,6 @@ class Sequence(Compound): """ def _uncached_match(self, text, pos, cache, error): new_pos = pos - length_of_sequence = 0 children = [] for m in self.members: node = m.match_core(text, new_pos, cache, error) @@ -336,12 +364,11 @@ def _uncached_match(self, text, pos, cache, error): children.append(node) length = node.end - node.start new_pos += length - length_of_sequence += length # Hooray! We got through all the members! - return Node(self, text, pos, pos + length_of_sequence, children) + return Node(self, text, pos, new_pos, children) def _as_rhs(self): - return u'({0})'.format(u' '.join(self._unicode_members())) + return '({0})'.format(' '.join(self._unicode_members())) class OneOf(Compound): @@ -359,112 +386,91 @@ def _uncached_match(self, text, pos, cache, error): return Node(self, text, pos, node.end, children=[node]) def _as_rhs(self): - return u'({0})'.format(u' / '.join(self._unicode_members())) + return '({0})'.format(' / '.join(self._unicode_members())) class Lookahead(Compound): """An expression which consumes nothing, even if its contained expression succeeds""" - # TODO: Merge this and Not for better cache hit ratios and less code. - # Downside: pretty-printed grammars might be spelled differently than what - # went in. That doesn't bother me. - - def _uncached_match(self, text, pos, cache, error): - node = self.members[0].match_core(text, pos, cache, error) - if node is not None: - return Node(self, text, pos, pos) - - def _as_rhs(self): - return u'&%s' % self._unicode_members()[0] + __slots__ = ['negativity'] + def __init__(self, member, *, negative=False, **kwargs): + super().__init__(member, **kwargs) + self.negativity = bool(negative) -class Not(Compound): - """An expression that succeeds only if the expression within it doesn't - - In any case, it never consumes any characters; it's a negative lookahead. - - """ def _uncached_match(self, text, pos, cache, error): - # FWIW, the implementation in Parsing Techniques in Figure 15.29 does - # not bother to cache NOTs directly. node = self.members[0].match_core(text, pos, cache, error) - if node is None: + if (node is None) == self.negativity: # negative lookahead == match only if not found return Node(self, text, pos, pos) def _as_rhs(self): - # TODO: Make sure this parenthesizes the member properly if it's an OR - # or AND. - return u'!%s' % self._unicode_members()[0] - - -# Quantifiers. None of these is strictly necessary, but they're darn handy. - -class Optional(Compound): - """An expression that succeeds whether or not the contained one does - - If the contained expression succeeds, it goes ahead and consumes what it - consumes. Otherwise, it consumes nothing. - - """ - def _uncached_match(self, text, pos, cache, error): - node = self.members[0].match_core(text, pos, cache, error) - return (Node(self, text, pos, pos) if node is None else - Node(self, text, pos, node.end, children=[node])) - - def _as_rhs(self): - return u'%s?' % self._unicode_members()[0] - - -# TODO: Merge with OneOrMore. -class ZeroOrMore(Compound): - """An expression wrapper like the * quantifier in regexes.""" + return '%s%s' % ('!' if self.negativity else '&', self._unicode_members()[0]) - def _uncached_match(self, text, pos, cache, error): - new_pos = pos - children = [] - while True: - node = self.members[0].match_core(text, new_pos, cache, error) - if node is None or not (node.end - node.start): - # Node was None or 0 length. 0 would otherwise loop infinitely. - return Node(self, text, pos, new_pos, children) - children.append(node) - new_pos += node.end - node.start - - def _as_rhs(self): - return u'%s*' % self._unicode_members()[0] + def _eq_check_cycles(self, other, checked): + return ( + super()._eq_check_cycles(other, checked) and + self.negativity == other.negativity + ) +def Not(term): + return Lookahead(term, negative=True) -class OneOrMore(Compound): - """An expression wrapper like the + quantifier in regexes. +# Quantifiers. None of these is strictly necessary, but they're darn handy. - You can also pass in an alternate minimum to make this behave like "2 or - more", "3 or more", etc. +class Quantifier(Compound): + """An expression wrapper like the */+/?/{n,m} quantifier in regexes.""" - """ - __slots__ = ['min'] + __slots__ = ['min', 'max'] - # TODO: Add max. It should probably succeed if there are more than the max - # --just not consume them. - - def __init__(self, member, name='', min=1): - super(OneOrMore, self).__init__(member, name=name) + def __init__(self, member, *, min=0, max=float('inf'), name='', **kwargs): + super().__init__(member, name=name, **kwargs) self.min = min + self.max = max def _uncached_match(self, text, pos, cache, error): new_pos = pos children = [] - while True: + size = len(text) + while new_pos < size and len(children) < self.max: node = self.members[0].match_core(text, new_pos, cache, error) if node is None: - break + break # no more matches children.append(node) length = node.end - node.start - if length == 0: # Don't loop infinitely. + if len(children) >= self.min and length == 0: # Don't loop infinitely break new_pos += length if len(children) >= self.min: return Node(self, text, pos, new_pos, children) def _as_rhs(self): - return u'%s+' % self._unicode_members()[0] + if self.min == 0 and self.max == 1: + qualifier = '?' + elif self.min == 0 and self.max == float('inf'): + qualifier = '*' + elif self.min == 1 and self.max == float('inf'): + qualifier = '+' + elif self.max == float('inf'): + qualifier = '{%d,}' % self.min + elif self.min == 0: + qualifier = '{,%d}' % self.max + else: + qualifier = '{%d,%d}' % (self.min, self.max) + return '%s%s' % (self._unicode_members()[0], qualifier) + + def _eq_check_cycles(self, other, checked): + return ( + super()._eq_check_cycles(other, checked) and + self.min == other.min and + self.max == other.max + ) + +def ZeroOrMore(member, name=''): + return Quantifier(member, name=name, min=0, max=float('inf')) + +def OneOrMore(member, name='', min=1): + return Quantifier(member, name=name, min=min, max=float('inf')) + +def Optional(member, name=''): + return Quantifier(member, name=name, min=0, max=1) diff --git a/pydsdl/third_party/parsimonious/grammar.py b/pydsdl/third_party/parsimonious/grammar.py index b7b1c29..367f27e 100644 --- a/pydsdl/third_party/parsimonious/grammar.py +++ b/pydsdl/third_party/parsimonious/grammar.py @@ -6,18 +6,15 @@ """ from collections import OrderedDict -from inspect import isfunction, ismethod - -from six import (text_type, itervalues, iteritems, python_2_unicode_compatible, PY2) +from textwrap import dedent from parsimonious.exceptions import BadGrammar, UndefinedLabel from parsimonious.expressions import (Literal, Regex, Sequence, OneOf, - Lookahead, Optional, ZeroOrMore, OneOrMore, Not, TokenMatcher, - expression) + Lookahead, Quantifier, Optional, ZeroOrMore, OneOrMore, Not, TokenMatcher, + expression, is_callable) from parsimonious.nodes import NodeVisitor from parsimonious.utils import evaluate_string -@python_2_unicode_compatible class Grammar(OrderedDict): """A collection of rules that describe a language @@ -63,11 +60,11 @@ def __init__(self, rules='', **more_rules): """ decorated_custom_rules = { - k: (expression(v, k, self) if isfunction(v) or ismethod(v) else v) - for k, v in iteritems(more_rules)} + k: (expression(v, k, self) if is_callable(v) else v) + for k, v in more_rules.items()} exprs, first = self._expressions_from_rules(rules, decorated_custom_rules) - super(Grammar, self).__init__(exprs.items()) + super().__init__(exprs.items()) self.default_rule = first # may be None def default(self, rule_name): @@ -85,7 +82,7 @@ def _copy(self): """ new = Grammar.__new__(Grammar) - super(Grammar, new).__init__(iteritems(self)) + super(Grammar, new).__init__(self.items()) new.default_rule = self.default_rule return new @@ -135,14 +132,13 @@ def __str__(self): """Return a rule string that, when passed to the constructor, would reconstitute the grammar.""" exprs = [self.default_rule] if self.default_rule else [] - exprs.extend(expr for expr in itervalues(self) if + exprs.extend(expr for expr in self.values() if expr is not self.default_rule) return '\n'.join(expr.as_rule() for expr in exprs) def __repr__(self): """Return an expression that will reconstitute the grammar.""" - codec = 'string_escape' if PY2 else 'unicode_escape' - return "Grammar('%s')" % str(self).encode(codec) + return "Grammar({!r})".format(str(self)) class TokenGrammar(Grammar): @@ -190,7 +186,7 @@ def _expressions_from_rules(self, rule_syntax, custom_rules): literal = Sequence(spaceless_literal, _, name='literal') regex = Sequence(Literal('~'), literal, - Regex('[ilmsux]*', ignore_case=True), + Regex('[ilmsuxa]*', ignore_case=True), _, name='regex') atom = OneOf(reference, literal, regex, name='atom') @@ -231,8 +227,8 @@ def _expressions_from_rules(self, rule_syntax, custom_rules): literal = spaceless_literal _ # So you can't spell a regex like `~"..." ilm`: - spaceless_literal = ~"u?r?\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\""is / - ~"u?r?'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"is + spaceless_literal = ~"u?r?b?\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\""is / + ~"u?r?b?'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"is expression = ored / sequence / term or_term = "/" _ term @@ -243,15 +239,15 @@ def _expressions_from_rules(self, rule_syntax, custom_rules): term = not_term / lookahead_term / quantified / atom quantified = atom quantifier atom = reference / literal / regex / parenthesized - regex = "~" spaceless_literal ~"[ilmsux]*"i _ + regex = "~" spaceless_literal ~"[ilmsuxa]*"i _ parenthesized = "(" _ expression ")" _ - quantifier = ~"[*+?]" _ + quantifier = ~r"[*+?]|\{\d*,\d+\}|\{\d+,\d*\}|\{\d+\}" _ reference = label !equals # A subsequent equal sign is the only thing that distinguishes a label # (which begins a new rule) from a reference (which is just a pointer to a # rule defined somewhere else): - label = ~"[a-zA-Z_][a-zA-Z_0-9]*" _ + label = ~"[a-zA-Z_][a-zA-Z_0-9]*(?![\"'])" _ # _ = ~r"\s*(?:#[^\r\n]*)?\s*" _ = meaninglessness* @@ -260,15 +256,43 @@ def _expressions_from_rules(self, rule_syntax, custom_rules): ''') -class LazyReference(text_type): +class LazyReference(str): """A lazy reference to a rule, which we resolve after grokking all the rules""" - name = u'' + name = '' + + def resolve_refs(self, rule_map): + """ + Traverse the rule map following top-level lazy references, + until we reach a cycle (raise an error) or a concrete expression. + + For example, the following is a circular reference: + foo = bar + baz = foo2 + foo2 = foo + + Note that every RHS of a grammar rule _must_ be either a + LazyReference or a concrete expression, so the reference chain will + eventually either terminate or find a cycle. + """ + seen = set() + cur = self + while True: + if cur in seen: + raise BadGrammar(f"Circular Reference resolving {self.name}={self}.") + else: + seen.add(cur) + try: + cur = rule_map[str(cur)] + except KeyError: + raise UndefinedLabel(cur) + if not isinstance(cur, LazyReference): + return cur # Just for debugging: def _as_rhs(self): - return u'' % self + return '' % self class RuleVisitor(NodeVisitor): @@ -291,6 +315,7 @@ def __init__(self, custom_rules=None): """ self.custom_rules = custom_rules or {} + self._last_literal_node_and_type = None def visit_parenthesized(self, node, parenthesized): """Treat a parenthesized subexpression as just its contents. @@ -308,7 +333,17 @@ def visit_quantifier(self, node, quantifier): def visit_quantified(self, node, quantified): atom, quantifier = quantified - return self.quantifier_classes[quantifier.text](atom) + try: + return self.quantifier_classes[quantifier.text](atom) + except KeyError: + # This should pass: assert re.full_match("\{(\d*)(,(\d*))?\}", quantifier) + quantifier = quantifier.text[1:-1].split(",") + if len(quantifier) == 1: + min_match = max_match = int(quantifier[0]) + else: + min_match = int(quantifier[0]) if quantifier[0] else 0 + max_match = int(quantifier[1]) if quantifier[1] else float('inf') + return Quantifier(atom, min=min_match, max=max_match) def visit_lookahead_term(self, node, lookahead_term): ampersand, term, _ = lookahead_term @@ -368,11 +403,24 @@ def visit_regex(self, node, regex): multiline='M' in flags, dot_all='S' in flags, unicode='U' in flags, - verbose='X' in flags) + verbose='X' in flags, + ascii='A' in flags) def visit_spaceless_literal(self, spaceless_literal, visited_children): """Turn a string literal into a ``Literal`` that recognizes it.""" - return Literal(evaluate_string(spaceless_literal.text)) + literal_value = evaluate_string(spaceless_literal.text) + if self._last_literal_node_and_type: + last_node, last_type = self._last_literal_node_and_type + if last_type != type(literal_value): + raise BadGrammar(dedent(f"""\ + Found {last_node.text} ({last_type}) and {spaceless_literal.text} ({type(literal_value)}) string literals. + All strings in a single grammar must be of the same type. + """) + ) + + self._last_literal_node_and_type = spaceless_literal, type(literal_value) + + return Literal(literal_value) def visit_literal(self, node, literal): """Pick just the literal out of a literal-and-junk combo.""" @@ -395,35 +443,6 @@ def generic_visit(self, node, visited_children): """ return visited_children or node # should semantically be a tuple - def _resolve_refs(self, rule_map, expr, done): - """Return an expression with all its lazy references recursively - resolved. - - Resolve any lazy references in the expression ``expr``, recursing into - all subexpressions. - - :arg done: The set of Expressions that have already been or are - currently being resolved, to ward off redundant work and prevent - infinite recursion for circular refs - - """ - if isinstance(expr, LazyReference): - label = text_type(expr) - try: - reffed_expr = rule_map[label] - except KeyError: - raise UndefinedLabel(expr) - return self._resolve_refs(rule_map, reffed_expr, done) - else: - if getattr(expr, 'members', ()) and expr not in done: - # Prevents infinite recursion for circular refs. At worst, one - # of `expr.members` can refer back to `expr`, but it can't go - # any farther. - done.add(expr) - expr.members = tuple(self._resolve_refs(rule_map, member, done) - for member in expr.members) - return expr - def visit_rules(self, node, rules_list): """Collate all the rules into a map. Return (map, default rule). @@ -448,9 +467,11 @@ def visit_rules(self, node, rules_list): rule_map.update(self.custom_rules) # Resolve references. This tolerates forward references. - done = set() - rule_map = OrderedDict((expr.name, self._resolve_refs(rule_map, expr, done)) - for expr in itervalues(rule_map)) + for name, rule in list(rule_map.items()): + if hasattr(rule, 'resolve_refs'): + # Some custom rules may not define a resolve_refs method, + # though anything that inherits from Expression will have it. + rule_map[name] = rule.resolve_refs(rule_map) # isinstance() is a temporary hack around the fact that * rules don't # always get transformed into lists by NodeVisitor. We should fix that; diff --git a/pydsdl/third_party/parsimonious/nodes.py b/pydsdl/third_party/parsimonious/nodes.py index 8c8d1af..7839097 100644 --- a/pydsdl/third_party/parsimonious/nodes.py +++ b/pydsdl/third_party/parsimonious/nodes.py @@ -9,13 +9,9 @@ from inspect import isfunction from sys import version_info, exc_info -from six import reraise, python_2_unicode_compatible, with_metaclass, \ - iteritems - from parsimonious.exceptions import VisitationError, UndefinedLabel -@python_2_unicode_compatible class Node(object): """A parse tree node @@ -139,7 +135,7 @@ def unvisit(name): """Remove any leading "visit_" from a method name.""" return name[6:] if name.startswith('visit_') else name - methods = [v for k, v in iteritems(namespace) if + methods = [v for k, v in namespace.items() if hasattr(v, '_rule') and isfunction(v)] if methods: from parsimonious.grammar import Grammar # circular import dodge @@ -158,7 +154,7 @@ def unvisit(name): metaclass).__new__(metaclass, name, bases, namespace) -class NodeVisitor(with_metaclass(RuleDecoratorMeta, object)): +class NodeVisitor(object, metaclass=RuleDecoratorMeta): """A shell for writing things that turn parse trees into something useful Performs a depth-first traversal of an AST. Subclass this, add methods for @@ -218,13 +214,15 @@ def visit(self, node): except (VisitationError, UndefinedLabel): # Don't catch and re-wrap already-wrapped exceptions. raise - except self.unwrapped_exceptions: - raise - except Exception: + except Exception as exc: + # implentors may define exception classes that should not be + # wrapped. + if isinstance(exc, self.unwrapped_exceptions): + raise # Catch any exception, and tack on a parse tree so it's easier to # see where it went wrong. - exc_class, exc, tb = exc_info() - reraise(VisitationError, VisitationError(exc, exc_class, node), tb) + exc_class = type(exc) + raise VisitationError(exc, exc_class, node) from exc def generic_visit(self, node, visited_children): """Default visitor method diff --git a/pydsdl/third_party/parsimonious/utils.py b/pydsdl/third_party/parsimonious/utils.py index e31c902..721da69 100644 --- a/pydsdl/third_party/parsimonious/utils.py +++ b/pydsdl/third_party/parsimonious/utils.py @@ -2,8 +2,6 @@ import ast -from six import python_2_unicode_compatible - class StrAndRepr(object): """Mix-in which gives the class the same __repr__ and __str__.""" @@ -14,14 +12,15 @@ def __repr__(self): def evaluate_string(string): """Piggyback on Python's string support so we can have backslash escaping - and niceties like \n, \t, etc. string.decode('string_escape') would have - been a lower-level possibility. + and niceties like \n, \t, etc. + This also supports: + 1. b"strings", allowing grammars to parse bytestrings, in addition to str. + 2. r"strings" to simplify regexes. """ return ast.literal_eval(string) -@python_2_unicode_compatible class Token(StrAndRepr): """A class to represent tokens, for use with TokenGrammars @@ -37,7 +36,7 @@ def __init__(self, type): self.type = type def __str__(self): - return u'' % (self.type,) + return '' % (self.type,) def __eq__(self, other): return self.type == other.type diff --git a/pydsdl/third_party/six.py b/pydsdl/third_party/six.py deleted file mode 100644 index 4e15675..0000000 --- a/pydsdl/third_party/six.py +++ /dev/null @@ -1,998 +0,0 @@ -# Copyright (c) 2010-2020 Benjamin Peterson -# -# 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. - -"""Utilities for writing code that runs on Python 2 and 3""" - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.16.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - -if PY34: - from importlib.util import spec_from_loader -else: - spec_from_loader = None - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def find_spec(self, fullname, path, target=None): - if fullname in self.known_modules: - return spec_from_loader(fullname, self) - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - - def create_module(self, spec): - return self.load_module(spec.name) - - def exec_module(self, module): - pass - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("getoutput", "commands", "subprocess"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("splitvalue", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), - MovedAttribute("parse_http_list", "urllib2", "urllib.request"), - MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - del io - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - _assertNotRegex = "assertNotRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" - _assertNotRegex = "assertNotRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - _assertNotRegex = "assertNotRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -def assertNotRegex(self, *args, **kwargs): - return getattr(self, _assertNotRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - try: - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None - tb = None - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - try: - raise tp, value, tb - finally: - tb = None -""") - - -if sys.version_info[:2] > (3,): - exec_("""def raise_from(value, from_value): - try: - raise value from from_value - finally: - value = None -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - # This does exactly the same what the :func:`py3:functools.update_wrapper` - # function does on Python versions after 3.2. It sets the ``__wrapped__`` - # attribute on ``wrapper`` object and it doesn't raise an error if any of - # the attributes mentioned in ``assigned`` and ``updated`` are missing on - # ``wrapped`` object. - def _update_wrapper(wrapper, wrapped, - assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - for attr in assigned: - try: - value = getattr(wrapped, attr) - except AttributeError: - continue - else: - setattr(wrapper, attr, value) - for attr in updated: - getattr(wrapper, attr).update(getattr(wrapped, attr, {})) - wrapper.__wrapped__ = wrapped - return wrapper - _update_wrapper.__doc__ = functools.update_wrapper.__doc__ - - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - return functools.partial(_update_wrapper, wrapped=wrapped, - assigned=assigned, updated=updated) - wraps.__doc__ = functools.wraps.__doc__ - -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(type): - - def __new__(cls, name, this_bases, d): - if sys.version_info[:2] >= (3, 7): - # This version introduced PEP 560 that requires a bit - # of extra care (we mimic what is done by __build_class__). - resolved_bases = types.resolve_bases(bases) - if resolved_bases is not bases: - d['__orig_bases__'] = bases - else: - resolved_bases = bases - return meta(name, resolved_bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - if hasattr(cls, '__qualname__'): - orig_vars['__qualname__'] = cls.__qualname__ - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def ensure_binary(s, encoding='utf-8', errors='strict'): - """Coerce **s** to six.binary_type. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> encoded to `bytes` - - `bytes` -> `bytes` - """ - if isinstance(s, binary_type): - return s - if isinstance(s, text_type): - return s.encode(encoding, errors) - raise TypeError("not expecting type '%s'" % type(s)) - - -def ensure_str(s, encoding='utf-8', errors='strict'): - """Coerce *s* to `str`. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - # Optimization: Fast return for the common case. - if type(s) is str: - return s - if PY2 and isinstance(s, text_type): - return s.encode(encoding, errors) - elif PY3 and isinstance(s, binary_type): - return s.decode(encoding, errors) - elif not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) - return s - - -def ensure_text(s, encoding='utf-8', errors='strict'): - """Coerce *s* to six.text_type. - - For Python 2: - - `unicode` -> `unicode` - - `str` -> `unicode` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, binary_type): - return s.decode(encoding, errors) - elif isinstance(s, text_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) - - -def python_2_unicode_compatible(klass): - """ - A class decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/setup.cfg b/setup.cfg index 1dc3a9d..8d934ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,9 +69,6 @@ ignore_missing_imports = True ignore_errors = True implicit_reexport = True -[mypy-six] -ignore_errors = True - # -------------------------------------------------- COVERAGE -------------------------------------------------- [coverage:run] source = pydsdl diff --git a/update_third_party_subtrees.sh b/update_third_party_subtrees.sh index 0ea8c2f..797bff3 100755 --- a/update_third_party_subtrees.sh +++ b/update_third_party_subtrees.sh @@ -12,9 +12,6 @@ rm -rf $THIRD_PARTY_DIR/* &> /dev/null # Updating Parsimonious. parsimonious_tag="$1" [ -n "$parsimonious_tag" ] || exit 1 -git fetch https://github.com/erikrose/parsimonious $parsimonious_tag || exit 2 +git fetch https://github.com/erikrose/parsimonious "$parsimonious_tag" || exit 2 git read-tree --prefix=$THIRD_PARTY_DIR/parsimonious/ -vu FETCH_HEAD:parsimonious || exit 3 rm -rf $THIRD_PARTY_DIR/parsimonious/tests/ # We don't want to keep its tests around, they're no use for us anyway. - -# Updating six.py, needed for Parsimonious only. -wget https://raw.githubusercontent.com/benjaminp/six/1.15.0/six.py -P $THIRD_PARTY_DIR || exit 4