diff --git a/refex/python/matcher.py b/refex/python/matcher.py index c8c90b1..a27432c 100644 --- a/refex/python/matcher.py +++ b/refex/python/matcher.py @@ -182,9 +182,7 @@ def _coerce_list(values): _IS_SUBMATCHER_LIST_ATTRIB = __name__ + '._IS_SUBMATCHER_LIST_ATTRIB' -def submatcher_attrib( - *args, - **kwargs: Any): # TODO: make walk a kwarg when Py2 support is dropped. +def submatcher_attrib(*args, walk: bool = True, **kwargs: Any): """Creates an attr.ib that is marked as a submatcher. This will cause the matcher to be automatically walked as part of the @@ -199,15 +197,13 @@ def submatcher_attrib( Returns: An attr.ib() """ - if kwargs.pop('walk', True): + if walk: kwargs.setdefault('metadata', {})[_IS_SUBMATCHER_ATTRIB] = True kwargs.setdefault('converter', coerce) return attr.ib(*args, **kwargs) -def submatcher_list_attrib( - *args, - **kwargs: Any): # TODO: make walk a kwarg when Py2 support is dropped. +def submatcher_list_attrib(*args, walk: bool = True, **kwargs: Any): """Creates an attr.ib that is marked as an iterable of submatchers. This will cause the matcher to be automatically walked as part of the @@ -222,7 +218,7 @@ def submatcher_list_attrib( Returns: An attr.ib() """ - if kwargs.pop('walk', True): + if walk: kwargs.setdefault('metadata', {})[_IS_SUBMATCHER_LIST_ATTRIB] = True kwargs.setdefault('converter', _coerce_list) return attr.ib(*args, **kwargs) @@ -666,8 +662,8 @@ class MatchInfo(object): """ match = attr.ib(type=_match.Match) # TODO: also add a top-level `replacement` variable, replacing the magic root. - bindings = attr.ib(factory=dict, type=Dict[str, _match.Match]) - replacements = attr.ib(factory=dict, type=Dict[str, _match.Match]) + bindings = attr.ib(factory=dict, type=Dict[str, BoundValue]) + replacements = attr.ib(factory=dict, type=Dict[str, formatting.Template]) def _stringify_candidate(context, candidate): diff --git a/refex/python/syntactic_template.py b/refex/python/syntactic_template.py index 58898a1..7a2e190 100644 --- a/refex/python/syntactic_template.py +++ b/refex/python/syntactic_template.py @@ -133,8 +133,8 @@ class _BasePythonTemplate(formatting.Template): template: The source template """ template = attr.ib(type=Text) - _lexical_template = attr.ib(repr=False, init=False) # type: _LexicalTemplate - _ast_matcher = attr.ib(repr=False, init=False) # type: matcher.Matcher + _lexical_template = attr.ib(repr=False, init=False, type=_LexicalTemplate) + _ast_matcher = attr.ib(repr=False, init=False, type=matcher.Matcher) def __attrs_post_init__(self): if not isinstance(self.template, six.text_type): diff --git a/refex/rxerr_debug.py b/refex/rxerr_debug.py index 508b8a0..bc26713 100644 --- a/refex/rxerr_debug.py +++ b/refex/rxerr_debug.py @@ -46,18 +46,19 @@ def main(argv): source = failure['content'] except KeyError: pass - with tempfile.NamedTemporaryFile( - mode='w', encoding='utf-8', suffix='.py', delete=False) as out_f: - out_f.write(source) - print('Content:', out_f.name) + else: + with tempfile.NamedTemporaryFile( + mode='w', encoding='utf-8', suffix='.py', delete=False) as out_f: + out_f.write(source) + print('Content:', out_f.name) try: tb = failure['traceback'] except KeyError: pass else: - print( - pygments.highlight(tb, lexers.PythonTracebackLexer(), - formatters.Terminal256Formatter())) + lexer = lexers.PythonTracebackLexer() # pytype: disable=module-attr + formatter = formatters.Terminal256Formatter() # pytype: disable=module-attr + print(pygments.highlight(tb, lexer, formatter)) if __name__ == '__main__': diff --git a/refex/search.py b/refex/search.py index 1132ce0..2a237ff 100644 --- a/refex/search.py +++ b/refex/search.py @@ -420,7 +420,7 @@ class CombinedSearcher(AbstractSearcher): # could walk once and only run the searchers that could possibly match # at a given point using an O(1) type lookup -- which would generally cut # down the number of results. - searchers = attr.ib(type=Tuple[AbstractSearcher, ...], converter=tuple,) + searchers = attr.ib(type=Sequence[AbstractSearcher], converter=tuple) def parse(self, data: Text, filename: str): """Parses using each sub-searcher, returning the most specific parsed file. @@ -630,7 +630,7 @@ def find_dicts_parsed( def key_span_for_dict( self, parsed: parsed_file.ParsedFile, - match_dict: Iterable[Mapping[MatchKey, match.Match]], + match_dict: Mapping[MatchKey, match.Match], ) -> Optional[Tuple[int, int]]: """Returns the ``key_span`` that the final ``Substitution`` will have.""" return None @@ -682,12 +682,10 @@ def __attrs_post_init__(self): missing_labels = formatting.template_variables( self.templates) - pattern_labels if missing_labels: + groups = ', '.join(f'`{g}`' for g in sorted(map(str, missing_labels))) raise ValueError( - 'The substitution template(s) referenced groups not available in the regex (`{self._compiled.pattern}`): {groups}' - .format( - self=self, - groups=', '.join( - '`{}`'.format(g) for g in sorted(map(str, missing_labels))))) + f'The substitution template(s) referenced groups not available in the regex (`{self._compiled.pattern}`): {groups}' + ) @classmethod def from_pattern(cls, pattern: str, templates: Optional[Dict[str, formatting.Template]]): @@ -734,7 +732,7 @@ class BasePythonRewritingSearcher(BasePythonSearcher, BaseRewritingSearcher): _matcher = attr.ib() @classmethod - def from_matcher(cls, matcher, templates: Optional[Dict[str, formatting.Template]]): + def from_matcher(cls, matcher, templates: Dict[str, formatting.Template]): """Creates a searcher from an evaluated matcher, and adds a root label.""" # We wrap the evaluated matcher in a SystemBind() that is sort of like # "group 0" for regexes. diff --git a/refex/substitution.py b/refex/substitution.py index 9f79fa4..2434674 100644 --- a/refex/substitution.py +++ b/refex/substitution.py @@ -150,7 +150,7 @@ def _validate(self): expected_type=six.text_type.__name__, actual_type=type(replacement).__name__)) - def relative_to_span(self, start: int, end: int) -> "Substitution": + def relative_to_span(self, start: int, end: int) -> Optional['Substitution']: """Returns a new substitution that is offset relative to the provided span. If ``sub`` is a :class:`Substitution` for ``s``, then diff --git a/refex/test_rxerr_debug.py b/refex/test_rxerr_debug.py index 05dffdc..f86e865 100644 --- a/refex/test_rxerr_debug.py +++ b/refex/test_rxerr_debug.py @@ -29,6 +29,24 @@ def test_argv(self): # Instead, we can just run shlex.split() over it as a quick safety check. self.assertEqual(shlex.split(stdout.getvalue()), ['Command:'] + argv) + def test_traceback(self): + """Tests that the traceback shows up, ish.""" + tb = ('Traceback (most recent call last):\n' + ' File "", line 1, in \n' + 'SomeError: description\n') + path = self.create_tempfile( + content=json.dumps({'failures': { + 'path': { + 'traceback': tb + } + }})).full_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + rxerr_debug.main(['rxerr_debug', path]) + stdout = stdout.getvalue() + self.assertIn('SomeError', stdout) + self.assertIn('description', stdout) + if __name__ == '__main__': absltest.main()