diff options
author | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-10 06:57:36 -0500 |
---|---|---|
committer | Paul McGuire <ptmcg@austin.rr.com> | 2019-08-10 06:57:36 -0500 |
commit | 675e87a859ce9f7bfefb091e9d6198c4a7f5eafd (patch) | |
tree | a980f282c739cf701283d08b2559886e2dc7d009 | |
parent | 7c1db54c6b4de188d7bebcc7372b926a13fd3da4 (diff) | |
download | pyparsing-git-675e87a859ce9f7bfefb091e9d6198c4a7f5eafd.tar.gz |
Rework __diag__ and __compat__ to be actual classes instead of just namespaces, to add helpful behavior and methods
-rw-r--r-- | CHANGES | 21 | ||||
-rw-r--r-- | pyparsing.py | 75 | ||||
-rw-r--r-- | unitTests.py | 147 |
3 files changed, 180 insertions, 63 deletions
@@ -33,6 +33,23 @@ Version 2.5.0a1 to help identify those expressions in your parsers that will have changed as a result. +- Expanded __diag__ and __compat__ to actual classes instead of + just namespaces, to add some helpful behavior: + - enable() and .disable() methods to give extra + help when setting or clearing flags (detects invalid + flag names, detects when trying to set a __compat__ flag + that is no longer settable). Use these methods now to + set or clear flags, instead of directly setting to True or + False. + + import pyparsing as pp + pp.__diag__.enable("warn_multiple_tokens_in_named_alternation") + + - __diag__.enable_all_warnings() is another helper that sets + all "warn*" diagnostics to True. + + pp.__diag__.enable_all_warnings() + - Fixed handling of ParseSyntaxExceptions raised as part of Each expressions, when sub-expressions contain '-' backtrack suppression. As part of resolution to a question posted by John @@ -41,6 +58,10 @@ Version 2.5.0a1 - Fixed bug in CloseMatch where end location was incorrectly computed; and updated partial_gene_match.py example. +- Fixed bug in indentedBlock with a parser using two different + types of nested indented blocks with different indent values, + but sharing the same indent stack, submitted by renzbagaporo. + - BigQueryViewParser.py added to examples directory, PR submitted by Michael Smedberg, nice work! diff --git a/pyparsing.py b/pyparsing.py index aa49a06..43f1abc 100644 --- a/pyparsing.py +++ b/pyparsing.py @@ -96,7 +96,7 @@ classes inherit from. Use the docstrings for examples of how to: """ __version__ = "2.5.0a1" -__versionTime__ = "09 Aug 2019 11:27 UTC" +__versionTime__ = "10 Aug 2019 11:56 UTC" __author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" import string @@ -127,24 +127,52 @@ from collections.abc import MutableMapping, Mapping from collections import OrderedDict from types import SimpleNamespace -# version compatibility configuration -__compat__ = SimpleNamespace() -__compat__.__doc__ = """ + +class __config_flags: + """Internal class for defining compatibility and debugging flags""" + _all_names = [] + _fixed_names = [] + _type_desc = "configuration" + + @classmethod + def _set(cls, dname, value): + if dname in cls._fixed_names: + warnings.warn("{}.{} {} is {} and cannot be overridden".format(cls.__name__, + dname, + cls._type_desc, + str(getattr(cls, dname)).upper())) + if dname in cls._all_names: + setattr(cls, dname, value) + else: + raise ValueError("no such {} {!r}".format(cls._type_desc, dname)) + + enable = classmethod(lambda cls, name: cls._set(name, True)) + disable = classmethod(lambda cls, name: cls._set(name, False)) + +class __compat__(__config_flags): + """ A cross-version compatibility configuration for pyparsing features that will be released in a future version. By setting values in this configuration to True, those features can be enabled in prior versions for compatibility development and testing. - collect_all_And_tokens - flag to enable fix for Issue #63 that fixes erroneous grouping - of results names when an And expression is nested within an Or or MatchFirst; + of results names when an And expression is nested within an Or or MatchFirst; maintained for compatibility, but setting to False no longer restores pre-2.3.1 behavior -""" -__compat__.collect_all_And_tokens = True + """ + _type_desc = "compatibility" + + collect_all_And_tokens = True -__diag__ = SimpleNamespace() -__diag__.__doc__ = """ -Diagnostic configuration (all default to False) + _all_names = [__ for __ in locals() if not __.startswith('_')] + _fixed_names = """ + collect_all_And_tokens + """.split() + +class __diag__(__config_flags): + """ + Diagnostic configuration (all default to False) - warn_multiple_tokens_in_named_alternation - flag to enable warnings when a results name is defined on a MatchFirst or Or expression with one or more And subexpressions - warn_ungrouped_named_tokens_in_collection - flag to enable warnings when a results @@ -156,12 +184,27 @@ Diagnostic configuration (all default to False) incorrectly called with multiple str arguments - enable_debug_on_named_expressions - flag to auto-enable debug on all subsequent calls to ParserElement.setName() -""" -__diag__.warn_multiple_tokens_in_named_alternation = False -__diag__.warn_ungrouped_named_tokens_in_collection = False -__diag__.warn_name_set_on_empty_Forward = False -__diag__.warn_on_multiple_string_args_to_oneof = False -__diag__.enable_debug_on_named_expressions = False + """ + _type_desc = "diagnostic" + + warn_multiple_tokens_in_named_alternation = False + warn_ungrouped_named_tokens_in_collection = False + warn_name_set_on_empty_Forward = False + warn_on_multiple_string_args_to_oneof = False + enable_debug_on_named_expressions = False + + _all_names = [__ for __ in locals() if not __.startswith('_')] + _warning_names = [name for name in _all_names if name.startswith("warn")] + _debug_names = [name for name in _all_names if name.startswith("enable_debug")] + + @classmethod + def enable_all_warnings(cls): + for name in cls._warning_names: + cls.enable(name) + +# hide abstract class +del __config_flags + # ~ sys.stderr.write("testing pyparsing module, version %s, %s\n" % (__version__, __versionTime__)) diff --git a/unitTests.py b/unitTests.py index 8601be1..a081480 100644 --- a/unitTests.py +++ b/unitTests.py @@ -4356,6 +4356,17 @@ class IndentedBlockScanTest(ParseTestCase): self.assertEqual(len(r6), 1) +class InvalidDiagSettingTest(ParseTestCase): + def runTest(self): + import pyparsing as pp + + with self.assertRaises(ValueError, msg="failed to raise exception when setting non-existent __diag__"): + pp.__diag__.enable("xyzzy") + + with self.assertWarns(UserWarning, msg="failed to warn disabling 'collect_all_And_tokens"): + pp.__compat__.disable("collect_all_And_tokens") + + class ParseResultsWithNameMatchFirst(ParseTestCase): def runTest(self): import pyparsing as pp @@ -4370,9 +4381,10 @@ class ParseResultsWithNameMatchFirst(ParseTestCase): self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) # test compatibility mode, no longer restoring pre-2.3.1 behavior - with AutoReset(pp.__compat__, "collect_all_And_tokens"): + with AutoReset(pp.__compat__, "collect_all_And_tokens"), \ + AutoReset(pp.__diag__, "warn_multiple_tokens_in_named_alternation"): pp.__compat__.collect_all_And_tokens = False - pp.__diag__.warn_multiple_tokens_in_named_alternation = True + pp.__diag__.enable("warn_multiple_tokens_in_named_alternation") expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird') expr_b = pp.Literal('the') + pp.Literal('bird') if PY_3: @@ -4410,9 +4422,10 @@ class ParseResultsWithNameOr(ParseTestCase): self.assertEqual(list(expr.parseString('the bird')['rexp']), 'the bird'.split()) # test compatibility mode, no longer restoring pre-2.3.1 behavior - with AutoReset(pp.__compat__, "collect_all_And_tokens"): + with AutoReset(pp.__compat__, "collect_all_And_tokens"), \ + AutoReset(pp.__diag__, "warn_multiple_tokens_in_named_alternation"): pp.__compat__.collect_all_And_tokens = False - pp.__diag__.warn_multiple_tokens_in_named_alternation = True + pp.__diag__.enable("warn_multiple_tokens_in_named_alternation") expr_a = pp.Literal('not') + pp.Literal('the') + pp.Literal('bird') expr_b = pp.Literal('the') + pp.Literal('bird') if PY_3: @@ -4562,16 +4575,17 @@ class WarnUngroupedNamedTokensTest(ParseTestCase): import pyparsing as pp ppc = pp.pyparsing_common - pp.__diag__.warn_ungrouped_named_tokens_in_collection = True + with AutoReset(pp.__diag__, "warn_ungrouped_named_tokens_in_collection"): + pp.__diag__.enable("warn_ungrouped_named_tokens_in_collection") - COMMA = pp.Suppress(',').setName("comma") - coord = (ppc.integer('x') + COMMA + ppc.integer('y')) + COMMA = pp.Suppress(',').setName("comma") + coord = (ppc.integer('x') + COMMA + ppc.integer('y')) - # this should emit a warning - if PY_3: - with self.assertWarns(UserWarning, msg="failed to warn with named repetition of" - " ungrouped named expressions"): - path = coord[...].setResultsName('path') + # this should emit a warning + if PY_3: + with self.assertWarns(UserWarning, msg="failed to warn with named repetition of" + " ungrouped named expressions"): + path = coord[...].setResultsName('path') class WarnNameSetOnEmptyForwardTest(ParseTestCase): @@ -4582,13 +4596,14 @@ class WarnNameSetOnEmptyForwardTest(ParseTestCase): def runTest(self): import pyparsing as pp - pp.__diag__.warn_name_set_on_empty_Forward = True + with AutoReset(pp.__diag__, "warn_name_set_on_empty_Forward"): + pp.__diag__.enable("warn_name_set_on_empty_Forward") - base = pp.Forward() + base = pp.Forward() - if PY_3: - with self.assertWarns(UserWarning, msg="failed to warn when naming an empty Forward expression"): - base("x") + if PY_3: + with self.assertWarns(UserWarning, msg="failed to warn when naming an empty Forward expression"): + base("x") class WarnOnMultipleStringArgsToOneOfTest(ParseTestCase): @@ -4599,11 +4614,12 @@ class WarnOnMultipleStringArgsToOneOfTest(ParseTestCase): def runTest(self): import pyparsing as pp - pp.__diag__.warn_on_multiple_string_args_to_oneof = True + with AutoReset(pp.__diag__, "warn_on_multiple_string_args_to_oneof"): + pp.__diag__.enable("warn_on_multiple_string_args_to_oneof") - if PY_3: - with self.assertWarns(UserWarning, msg="failed to warn when incorrectly calling oneOf(string, string)"): - a = pp.oneOf('A', 'B') + if PY_3: + with self.assertWarns(UserWarning, msg="failed to warn when incorrectly calling oneOf(string, string)"): + a = pp.oneOf('A', 'B') class EnableDebugOnNamedExpressionsTest(ParseTestCase): @@ -4615,33 +4631,34 @@ class EnableDebugOnNamedExpressionsTest(ParseTestCase): import pyparsing as pp import textwrap - test_stdout = StringIO() - - with AutoReset(sys, 'stdout', 'stderr'): - sys.stdout = test_stdout - sys.stderr = test_stdout - - pp.__diag__.enable_debug_on_named_expressions = True - integer = pp.Word(pp.nums).setName('integer') + with AutoReset(pp.__diag__, "enable_debug_on_named_expressions"): + test_stdout = StringIO() - integer[...].parseString("1 2 3") - - expected_debug_output = textwrap.dedent("""\ - Match integer at loc 0(1,1) - Matched integer -> ['1'] - Match integer at loc 1(1,2) - Matched integer -> ['2'] - Match integer at loc 3(1,4) - Matched integer -> ['3'] - Match integer at loc 5(1,6) - Exception raised:Expected integer, found end of text (at char 5), (line:1, col:6) - """) - output = test_stdout.getvalue() - print_(output) - self.assertEquals(output, - expected_debug_output, - "failed to auto-enable debug on named expressions " - "using enable_debug_on_named_expressions") + with AutoReset(sys, 'stdout', 'stderr'): + sys.stdout = test_stdout + sys.stderr = test_stdout + + pp.__diag__.enable("enable_debug_on_named_expressions") + integer = pp.Word(pp.nums).setName('integer') + + integer[...].parseString("1 2 3") + + expected_debug_output = textwrap.dedent("""\ + Match integer at loc 0(1,1) + Matched integer -> ['1'] + Match integer at loc 1(1,2) + Matched integer -> ['2'] + Match integer at loc 3(1,4) + Matched integer -> ['3'] + Match integer at loc 5(1,6) + Exception raised:Expected integer, found end of text (at char 5), (line:1, col:6) + """) + output = test_stdout.getvalue() + print_(output) + self.assertEquals(output, + expected_debug_output, + "failed to auto-enable debug on named expressions " + "using enable_debug_on_named_expressions") class UndesirableButCommonPracticesTest(ParseTestCase): @@ -4671,6 +4688,42 @@ class UndesirableButCommonPracticesTest(ParseTestCase): abc """) +class EnableWarnDiagsTest(ParseTestCase): + def runTest(self): + import pyparsing as pp + import pprint + + def filtered_vars(var_dict): + dunders = [nm for nm in var_dict if nm.startswith('__')] + return {k: v for k, v in var_dict.items() + if isinstance(v, bool) and k not in dunders} + + pprint.pprint(filtered_vars(vars(pp.__diag__)), width=30) + + warn_names = pp.__diag__._warning_names + other_names = pp.__diag__._debug_names + + # make sure they are off by default + for diag_name in warn_names: + self.assertFalse(getattr(pp.__diag__, diag_name), "__diag__.{} not set to True".format(diag_name)) + + with AutoReset(pp.__diag__, *warn_names): + # enable all warn_* diag_names + pp.__diag__.enable_all_warnings() + pprint.pprint(filtered_vars(vars(pp.__diag__)), width=30) + + # make sure they are on after being enabled + for diag_name in warn_names: + self.assertTrue(getattr(pp.__diag__, diag_name), "__diag__.{} not set to True".format(diag_name)) + + # non-warn diag_names must be enabled individually + for diag_name in other_names: + self.assertFalse(getattr(pp.__diag__, diag_name), "__diag__.{} not set to True".format(diag_name)) + + # make sure they are off after AutoReset + for diag_name in warn_names: + self.assertFalse(getattr(pp.__diag__, diag_name), "__diag__.{} not set to True".format(diag_name)) + class MiscellaneousParserTests(ParseTestCase): def runTest(self): |