Bump pyparsing from 3.0.9 to 3.1.1 (#2131)

* Bump pyparsing from 3.0.9 to 3.1.1

Bumps [pyparsing](https://github.com/pyparsing/pyparsing) from 3.0.9 to 3.1.1.
- [Release notes](https://github.com/pyparsing/pyparsing/releases)
- [Changelog](https://github.com/pyparsing/pyparsing/blob/master/CHANGES)
- [Commits](https://github.com/pyparsing/pyparsing/compare/pyparsing_3.0.9...3.1.1)

---
updated-dependencies:
- dependency-name: pyparsing
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update pyparsing==3.1.1

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com>

[skip ci]
This commit is contained in:
dependabot[bot] 2023-08-24 12:09:51 -07:00 committed by GitHub
parent d0c7f25a3f
commit 3debeada2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1306 additions and 797 deletions

View file

@ -56,7 +56,7 @@ self-explanatory class names, and the use of :class:`'+'<And>`,
:class:`'|'<MatchFirst>`, :class:`'^'<Or>` and :class:`'&'<Each>` operators. :class:`'|'<MatchFirst>`, :class:`'^'<Or>` and :class:`'&'<Each>` operators.
The :class:`ParseResults` object returned from The :class:`ParseResults` object returned from
:class:`ParserElement.parseString` can be :class:`ParserElement.parse_string` can be
accessed as a nested list, a dictionary, or an object with named accessed as a nested list, a dictionary, or an object with named
attributes. attributes.
@ -85,11 +85,11 @@ classes inherit from. Use the docstrings for examples of how to:
and :class:`'&'<Each>` operators to combine simple expressions into and :class:`'&'<Each>` operators to combine simple expressions into
more complex ones more complex ones
- associate names with your parsed results using - associate names with your parsed results using
:class:`ParserElement.setResultsName` :class:`ParserElement.set_results_name`
- access the parsed data, which is returned as a :class:`ParseResults` - access the parsed data, which is returned as a :class:`ParseResults`
object object
- find some helpful expression short-cuts like :class:`delimitedList` - find some helpful expression short-cuts like :class:`DelimitedList`
and :class:`oneOf` and :class:`one_of`
- find more useful common expressions in the :class:`pyparsing_common` - find more useful common expressions in the :class:`pyparsing_common`
namespace class namespace class
""" """
@ -106,30 +106,22 @@ class version_info(NamedTuple):
@property @property
def __version__(self): def __version__(self):
return ( return (
"{}.{}.{}".format(self.major, self.minor, self.micro) f"{self.major}.{self.minor}.{self.micro}"
+ ( + (
"{}{}{}".format( f"{'r' if self.releaselevel[0] == 'c' else ''}{self.releaselevel[0]}{self.serial}",
"r" if self.releaselevel[0] == "c" else "",
self.releaselevel[0],
self.serial,
),
"", "",
)[self.releaselevel == "final"] )[self.releaselevel == "final"]
) )
def __str__(self): def __str__(self):
return "{} {} / {}".format(__name__, self.__version__, __version_time__) return f"{__name__} {self.__version__} / {__version_time__}"
def __repr__(self): def __repr__(self):
return "{}.{}({})".format( return f"{__name__}.{type(self).__name__}({', '.join('{}={!r}'.format(*nv) for nv in zip(self._fields, self))})"
__name__,
type(self).__name__,
", ".join("{}={!r}".format(*nv) for nv in zip(self._fields, self)),
)
__version_info__ = version_info(3, 0, 9, "final", 0) __version_info__ = version_info(3, 1, 1, "final", 1)
__version_time__ = "05 May 2022 07:02 UTC" __version_time__ = "29 Jul 2023 22:27 UTC"
__version__ = __version_info__.__version__ __version__ = __version_info__.__version__
__versionTime__ = __version_time__ __versionTime__ = __version_time__
__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>" __author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
@ -139,9 +131,9 @@ from .exceptions import *
from .actions import * from .actions import *
from .core import __diag__, __compat__ from .core import __diag__, __compat__
from .results import * from .results import *
from .core import * from .core import * # type: ignore[misc, assignment]
from .core import _builtin_exprs as core_builtin_exprs from .core import _builtin_exprs as core_builtin_exprs
from .helpers import * from .helpers import * # type: ignore[misc, assignment]
from .helpers import _builtin_exprs as helper_builtin_exprs from .helpers import _builtin_exprs as helper_builtin_exprs
from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode from .unicode import unicode_set, UnicodeRangeList, pyparsing_unicode as unicode
@ -153,11 +145,11 @@ from .common import (
# define backward compat synonyms # define backward compat synonyms
if "pyparsing_unicode" not in globals(): if "pyparsing_unicode" not in globals():
pyparsing_unicode = unicode pyparsing_unicode = unicode # type: ignore[misc]
if "pyparsing_common" not in globals(): if "pyparsing_common" not in globals():
pyparsing_common = common pyparsing_common = common # type: ignore[misc]
if "pyparsing_test" not in globals(): if "pyparsing_test" not in globals():
pyparsing_test = testing pyparsing_test = testing # type: ignore[misc]
core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs core_builtin_exprs += common_builtin_exprs + helper_builtin_exprs
@ -174,7 +166,9 @@ __all__ = [
"CaselessKeyword", "CaselessKeyword",
"CaselessLiteral", "CaselessLiteral",
"CharsNotIn", "CharsNotIn",
"CloseMatch",
"Combine", "Combine",
"DelimitedList",
"Dict", "Dict",
"Each", "Each",
"Empty", "Empty",
@ -227,9 +221,11 @@ __all__ = [
"alphas8bit", "alphas8bit",
"any_close_tag", "any_close_tag",
"any_open_tag", "any_open_tag",
"autoname_elements",
"c_style_comment", "c_style_comment",
"col", "col",
"common_html_entity", "common_html_entity",
"condition_as_parse_action",
"counted_array", "counted_array",
"cpp_style_comment", "cpp_style_comment",
"dbl_quoted_string", "dbl_quoted_string",
@ -241,6 +237,7 @@ __all__ = [
"html_comment", "html_comment",
"identchars", "identchars",
"identbodychars", "identbodychars",
"infix_notation",
"java_style_comment", "java_style_comment",
"line", "line",
"line_end", "line_end",
@ -255,8 +252,12 @@ __all__ = [
"null_debug_action", "null_debug_action",
"nums", "nums",
"one_of", "one_of",
"original_text_for",
"printables", "printables",
"punc8bit", "punc8bit",
"pyparsing_common",
"pyparsing_test",
"pyparsing_unicode",
"python_style_comment", "python_style_comment",
"quoted_string", "quoted_string",
"remove_quotes", "remove_quotes",
@ -267,28 +268,20 @@ __all__ = [
"srange", "srange",
"string_end", "string_end",
"string_start", "string_start",
"token_map",
"trace_parse_action", "trace_parse_action",
"ungroup",
"unicode_set",
"unicode_string", "unicode_string",
"with_attribute", "with_attribute",
"indentedBlock",
"original_text_for",
"ungroup",
"infix_notation",
"locatedExpr",
"with_class", "with_class",
"CloseMatch",
"token_map",
"pyparsing_common",
"pyparsing_unicode",
"unicode_set",
"condition_as_parse_action",
"pyparsing_test",
# pre-PEP8 compatibility names # pre-PEP8 compatibility names
"__versionTime__", "__versionTime__",
"anyCloseTag", "anyCloseTag",
"anyOpenTag", "anyOpenTag",
"cStyleComment", "cStyleComment",
"commonHTMLEntity", "commonHTMLEntity",
"conditionAsParseAction",
"countedArray", "countedArray",
"cppStyleComment", "cppStyleComment",
"dblQuotedString", "dblQuotedString",
@ -296,9 +289,12 @@ __all__ = [
"delimitedList", "delimitedList",
"dictOf", "dictOf",
"htmlComment", "htmlComment",
"indentedBlock",
"infixNotation",
"javaStyleComment", "javaStyleComment",
"lineEnd", "lineEnd",
"lineStart", "lineStart",
"locatedExpr",
"makeHTMLTags", "makeHTMLTags",
"makeXMLTags", "makeXMLTags",
"matchOnlyAtCol", "matchOnlyAtCol",
@ -308,6 +304,7 @@ __all__ = [
"nullDebugAction", "nullDebugAction",
"oneOf", "oneOf",
"opAssoc", "opAssoc",
"originalTextFor",
"pythonStyleComment", "pythonStyleComment",
"quotedString", "quotedString",
"removeQuotes", "removeQuotes",
@ -317,15 +314,12 @@ __all__ = [
"sglQuotedString", "sglQuotedString",
"stringEnd", "stringEnd",
"stringStart", "stringStart",
"tokenMap",
"traceParseAction", "traceParseAction",
"unicodeString", "unicodeString",
"withAttribute", "withAttribute",
"indentedBlock",
"originalTextFor",
"infixNotation",
"locatedExpr",
"withClass", "withClass",
"tokenMap", "common",
"conditionAsParseAction", "unicode",
"autoname_elements", "testing",
] ]

View file

@ -1,7 +1,7 @@
# actions.py # actions.py
from .exceptions import ParseException from .exceptions import ParseException
from .util import col from .util import col, replaced_by_pep8
class OnlyOnce: class OnlyOnce:
@ -38,7 +38,7 @@ def match_only_at_col(n):
def verify_col(strg, locn, toks): def verify_col(strg, locn, toks):
if col(locn, strg) != n: if col(locn, strg) != n:
raise ParseException(strg, locn, "matched token not at column {}".format(n)) raise ParseException(strg, locn, f"matched token not at column {n}")
return verify_col return verify_col
@ -148,15 +148,13 @@ def with_attribute(*args, **attr_dict):
raise ParseException( raise ParseException(
s, s,
l, l,
"attribute {!r} has value {!r}, must be {!r}".format( f"attribute {attrName!r} has value {tokens[attrName]!r}, must be {attrValue!r}",
attrName, tokens[attrName], attrValue
),
) )
return pa return pa
with_attribute.ANY_VALUE = object() with_attribute.ANY_VALUE = object() # type: ignore [attr-defined]
def with_class(classname, namespace=""): def with_class(classname, namespace=""):
@ -195,13 +193,25 @@ def with_class(classname, namespace=""):
1 4 0 1 0 1 4 0 1 0
1,3 2,3 1,1 1,3 2,3 1,1
""" """
classattr = "{}:class".format(namespace) if namespace else "class" classattr = f"{namespace}:class" if namespace else "class"
return with_attribute(**{classattr: classname}) return with_attribute(**{classattr: classname})
# pre-PEP8 compatibility symbols # pre-PEP8 compatibility symbols
replaceWith = replace_with # fmt: off
removeQuotes = remove_quotes @replaced_by_pep8(replace_with)
withAttribute = with_attribute def replaceWith(): ...
withClass = with_class
matchOnlyAtCol = match_only_at_col @replaced_by_pep8(remove_quotes)
def removeQuotes(): ...
@replaced_by_pep8(with_attribute)
def withAttribute(): ...
@replaced_by_pep8(with_class)
def withClass(): ...
@replaced_by_pep8(match_only_at_col)
def matchOnlyAtCol(): ...
# fmt: on

View file

@ -1,6 +1,6 @@
# common.py # common.py
from .core import * from .core import *
from .helpers import delimited_list, any_open_tag, any_close_tag from .helpers import DelimitedList, any_open_tag, any_close_tag
from datetime import datetime from datetime import datetime
@ -22,17 +22,17 @@ class pyparsing_common:
Parse actions: Parse actions:
- :class:`convertToInteger` - :class:`convert_to_integer`
- :class:`convertToFloat` - :class:`convert_to_float`
- :class:`convertToDate` - :class:`convert_to_date`
- :class:`convertToDatetime` - :class:`convert_to_datetime`
- :class:`stripHTMLTags` - :class:`strip_html_tags`
- :class:`upcaseTokens` - :class:`upcase_tokens`
- :class:`downcaseTokens` - :class:`downcase_tokens`
Example:: Example::
pyparsing_common.number.runTests(''' pyparsing_common.number.run_tests('''
# any int or real number, returned as the appropriate type # any int or real number, returned as the appropriate type
100 100
-100 -100
@ -42,7 +42,7 @@ class pyparsing_common:
1e-12 1e-12
''') ''')
pyparsing_common.fnumber.runTests(''' pyparsing_common.fnumber.run_tests('''
# any int or real number, returned as float # any int or real number, returned as float
100 100
-100 -100
@ -52,19 +52,19 @@ class pyparsing_common:
1e-12 1e-12
''') ''')
pyparsing_common.hex_integer.runTests(''' pyparsing_common.hex_integer.run_tests('''
# hex numbers # hex numbers
100 100
FF FF
''') ''')
pyparsing_common.fraction.runTests(''' pyparsing_common.fraction.run_tests('''
# fractions # fractions
1/2 1/2
-3/4 -3/4
''') ''')
pyparsing_common.mixed_integer.runTests(''' pyparsing_common.mixed_integer.run_tests('''
# mixed fractions # mixed fractions
1 1
1/2 1/2
@ -73,8 +73,8 @@ class pyparsing_common:
''') ''')
import uuid import uuid
pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) pyparsing_common.uuid.set_parse_action(token_map(uuid.UUID))
pyparsing_common.uuid.runTests(''' pyparsing_common.uuid.run_tests('''
# uuid # uuid
12345678-1234-5678-1234-567812345678 12345678-1234-5678-1234-567812345678
''') ''')
@ -260,8 +260,8 @@ class pyparsing_common:
Example:: Example::
date_expr = pyparsing_common.iso8601_date.copy() date_expr = pyparsing_common.iso8601_date.copy()
date_expr.setParseAction(pyparsing_common.convertToDate()) date_expr.set_parse_action(pyparsing_common.convert_to_date())
print(date_expr.parseString("1999-12-31")) print(date_expr.parse_string("1999-12-31"))
prints:: prints::
@ -287,8 +287,8 @@ class pyparsing_common:
Example:: Example::
dt_expr = pyparsing_common.iso8601_datetime.copy() dt_expr = pyparsing_common.iso8601_datetime.copy()
dt_expr.setParseAction(pyparsing_common.convertToDatetime()) dt_expr.set_parse_action(pyparsing_common.convert_to_datetime())
print(dt_expr.parseString("1999-12-31T23:59:59.999")) print(dt_expr.parse_string("1999-12-31T23:59:59.999"))
prints:: prints::
@ -326,9 +326,9 @@ class pyparsing_common:
# strip HTML links from normal text # strip HTML links from normal text
text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>' text = '<td>More info at the <a href="https://github.com/pyparsing/pyparsing/wiki">pyparsing</a> wiki page</td>'
td, td_end = makeHTMLTags("TD") td, td_end = make_html_tags("TD")
table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end table_text = td + SkipTo(td_end).set_parse_action(pyparsing_common.strip_html_tags)("body") + td_end
print(table_text.parseString(text).body) print(table_text.parse_string(text).body)
Prints:: Prints::
@ -348,7 +348,7 @@ class pyparsing_common:
.streamline() .streamline()
.set_name("commaItem") .set_name("commaItem")
) )
comma_separated_list = delimited_list( comma_separated_list = DelimitedList(
Opt(quoted_string.copy() | _commasepitem, default="") Opt(quoted_string.copy() | _commasepitem, default="")
).set_name("comma separated list") ).set_name("comma separated list")
"""Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" """Predefined expression of 1 or more printable words or quoted strings, separated by commas."""
@ -363,7 +363,7 @@ class pyparsing_common:
url = Regex( url = Regex(
# https://mathiasbynens.be/demo/url-regex # https://mathiasbynens.be/demo/url-regex
# https://gist.github.com/dperini/729294 # https://gist.github.com/dperini/729294
r"^" + r"(?P<url>" +
# protocol identifier (optional) # protocol identifier (optional)
# short syntax // still required # short syntax // still required
r"(?:(?:(?P<scheme>https?|ftp):)?\/\/)" + r"(?:(?:(?P<scheme>https?|ftp):)?\/\/)" +
@ -405,18 +405,26 @@ class pyparsing_common:
r"(\?(?P<query>[^#]*))?" + r"(\?(?P<query>[^#]*))?" +
# fragment (optional) # fragment (optional)
r"(#(?P<fragment>\S*))?" + r"(#(?P<fragment>\S*))?" +
r"$" r")"
).set_name("url") ).set_name("url")
"""URL (http/https/ftp scheme)"""
# fmt: on # fmt: on
# pre-PEP8 compatibility names # pre-PEP8 compatibility names
convertToInteger = convert_to_integer convertToInteger = convert_to_integer
"""Deprecated - use :class:`convert_to_integer`"""
convertToFloat = convert_to_float convertToFloat = convert_to_float
"""Deprecated - use :class:`convert_to_float`"""
convertToDate = convert_to_date convertToDate = convert_to_date
"""Deprecated - use :class:`convert_to_date`"""
convertToDatetime = convert_to_datetime convertToDatetime = convert_to_datetime
"""Deprecated - use :class:`convert_to_datetime`"""
stripHTMLTags = strip_html_tags stripHTMLTags = strip_html_tags
"""Deprecated - use :class:`strip_html_tags`"""
upcaseTokens = upcase_tokens upcaseTokens = upcase_tokens
"""Deprecated - use :class:`upcase_tokens`"""
downcaseTokens = downcase_tokens downcaseTokens = downcase_tokens
"""Deprecated - use :class:`downcase_tokens`"""
_builtin_exprs = [ _builtin_exprs = [

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
# mypy: ignore-errors
import railroad import railroad
import pyparsing import pyparsing
import typing import typing
@ -17,11 +18,13 @@ import inspect
jinja2_template_source = """\ jinja2_template_source = """\
{% if not embed %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
{% endif %}
{% if not head %} {% if not head %}
<style type="text/css"> <style>
.railroad-heading { .railroad-heading {
font-family: monospace; font-family: monospace;
} }
@ -29,8 +32,10 @@ jinja2_template_source = """\
{% else %} {% else %}
{{ head | safe }} {{ head | safe }}
{% endif %} {% endif %}
{% if not embed %}
</head> </head>
<body> <body>
{% endif %}
{{ body | safe }} {{ body | safe }}
{% for diagram in diagrams %} {% for diagram in diagrams %}
<div class="railroad-group"> <div class="railroad-group">
@ -41,8 +46,10 @@ jinja2_template_source = """\
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% if not embed %}
</body> </body>
</html> </html>
{% endif %}
""" """
template = Template(jinja2_template_source) template = Template(jinja2_template_source)
@ -127,7 +134,7 @@ class EditablePartial(Generic[T]):
return self.func(*args, **kwargs) return self.func(*args, **kwargs)
def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str: def railroad_to_html(diagrams: List[NamedDiagram], embed=False, **kwargs) -> str:
""" """
Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams
:params kwargs: kwargs to be passed in to the template :params kwargs: kwargs to be passed in to the template
@ -137,13 +144,17 @@ def railroad_to_html(diagrams: List[NamedDiagram], **kwargs) -> str:
if diagram.diagram is None: if diagram.diagram is None:
continue continue
io = StringIO() io = StringIO()
try:
css = kwargs.get('css')
diagram.diagram.writeStandalone(io.write, css=css)
except AttributeError:
diagram.diagram.writeSvg(io.write) diagram.diagram.writeSvg(io.write)
title = diagram.name title = diagram.name
if diagram.index == 0: if diagram.index == 0:
title += " (root)" title += " (root)"
data.append({"title": title, "text": "", "svg": io.getvalue()}) data.append({"title": title, "text": "", "svg": io.getvalue()})
return template.render(diagrams=data, **kwargs) return template.render(diagrams=data, embed=embed, **kwargs)
def resolve_partial(partial: "EditablePartial[T]") -> T: def resolve_partial(partial: "EditablePartial[T]") -> T:
@ -398,7 +409,6 @@ def _apply_diagram_item_enhancements(fn):
show_results_names: bool = False, show_results_names: bool = False,
show_groups: bool = False, show_groups: bool = False,
) -> typing.Optional[EditablePartial]: ) -> typing.Optional[EditablePartial]:
ret = fn( ret = fn(
element, element,
parent, parent,
@ -555,9 +565,11 @@ def _to_diagram_element(
else: else:
ret = EditablePartial.from_call(railroad.Group, label="", item="") ret = EditablePartial.from_call(railroad.Group, label="", item="")
elif isinstance(element, pyparsing.TokenConverter): elif isinstance(element, pyparsing.TokenConverter):
ret = EditablePartial.from_call( label = type(element).__name__.lower()
AnnotatedItem, label=type(element).__name__.lower(), item="" if label == "tokenconverter":
) ret = EditablePartial.from_call(railroad.Sequence, items=[])
else:
ret = EditablePartial.from_call(AnnotatedItem, label=label, item="")
elif isinstance(element, pyparsing.Opt): elif isinstance(element, pyparsing.Opt):
ret = EditablePartial.from_call(railroad.Optional, item="") ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore): elif isinstance(element, pyparsing.OneOrMore):
@ -571,10 +583,12 @@ def _to_diagram_element(
elif isinstance(element, pyparsing.Empty) and not element.customName: elif isinstance(element, pyparsing.Empty) and not element.customName:
# Skip unnamed "Empty" elements # Skip unnamed "Empty" elements
ret = None ret = None
elif len(exprs) > 1: elif isinstance(element, pyparsing.ParseElementEnhance):
ret = EditablePartial.from_call(railroad.Sequence, items=[]) ret = EditablePartial.from_call(railroad.Sequence, items=[])
elif len(exprs) > 0 and not element_results_name: elif len(exprs) > 0 and not element_results_name:
ret = EditablePartial.from_call(railroad.Group, item="", label=name) ret = EditablePartial.from_call(railroad.Group, item="", label=name)
elif len(exprs) > 0:
ret = EditablePartial.from_call(railroad.Sequence, items=[])
else: else:
terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName) terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName)
ret = terminal ret = terminal

View file

@ -4,7 +4,13 @@ import re
import sys import sys
import typing import typing
from .util import col, line, lineno, _collapse_string_to_ranges from .util import (
col,
line,
lineno,
_collapse_string_to_ranges,
replaced_by_pep8,
)
from .unicode import pyparsing_unicode as ppu from .unicode import pyparsing_unicode as ppu
@ -19,6 +25,20 @@ _exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
class ParseBaseException(Exception): class ParseBaseException(Exception):
"""base exception class for all parsing runtime exceptions""" """base exception class for all parsing runtime exceptions"""
loc: int
msg: str
pstr: str
parser_element: typing.Any # "ParserElement"
args: typing.Tuple[str, int, typing.Optional[str]]
__slots__ = (
"loc",
"msg",
"pstr",
"parser_element",
"args",
)
# Performance tuning: we construct a *lot* of these, so keep this # Performance tuning: we construct a *lot* of these, so keep this
# constructor as small and fast as possible # constructor as small and fast as possible
def __init__( def __init__(
@ -35,7 +55,7 @@ class ParseBaseException(Exception):
else: else:
self.msg = msg self.msg = msg
self.pstr = pstr self.pstr = pstr
self.parser_element = self.parserElement = elem self.parser_element = elem
self.args = (pstr, loc, msg) self.args = (pstr, loc, msg)
@staticmethod @staticmethod
@ -64,7 +84,7 @@ class ParseBaseException(Exception):
if isinstance(exc, ParseBaseException): if isinstance(exc, ParseBaseException):
ret.append(exc.line) ret.append(exc.line)
ret.append(" " * (exc.column - 1) + "^") ret.append(" " * (exc.column - 1) + "^")
ret.append("{}: {}".format(type(exc).__name__, exc)) ret.append(f"{type(exc).__name__}: {exc}")
if depth > 0: if depth > 0:
callers = inspect.getinnerframes(exc.__traceback__, context=depth) callers = inspect.getinnerframes(exc.__traceback__, context=depth)
@ -74,7 +94,9 @@ class ParseBaseException(Exception):
f_self = frm.f_locals.get("self", None) f_self = frm.f_locals.get("self", None)
if isinstance(f_self, ParserElement): if isinstance(f_self, ParserElement):
if frm.f_code.co_name not in ("parseImpl", "_parseNoCache"): if not frm.f_code.co_name.startswith(
("parseImpl", "_parseNoCache")
):
continue continue
if id(f_self) in seen: if id(f_self) in seen:
continue continue
@ -82,21 +104,19 @@ class ParseBaseException(Exception):
self_type = type(f_self) self_type = type(f_self)
ret.append( ret.append(
"{}.{} - {}".format( f"{self_type.__module__}.{self_type.__name__} - {f_self}"
self_type.__module__, self_type.__name__, f_self
)
) )
elif f_self is not None: elif f_self is not None:
self_type = type(f_self) self_type = type(f_self)
ret.append("{}.{}".format(self_type.__module__, self_type.__name__)) ret.append(f"{self_type.__module__}.{self_type.__name__}")
else: else:
code = frm.f_code code = frm.f_code
if code.co_name in ("wrapper", "<module>"): if code.co_name in ("wrapper", "<module>"):
continue continue
ret.append("{}".format(code.co_name)) ret.append(code.co_name)
depth -= 1 depth -= 1
if not depth: if not depth:
@ -110,7 +130,7 @@ class ParseBaseException(Exception):
internal factory method to simplify creating one type of ParseException internal factory method to simplify creating one type of ParseException
from another - avoids having __init__ signature conflicts among subclasses from another - avoids having __init__ signature conflicts among subclasses
""" """
return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element)
@property @property
def line(self) -> str: def line(self) -> str:
@ -140,6 +160,15 @@ class ParseBaseException(Exception):
""" """
return col(self.loc, self.pstr) return col(self.loc, self.pstr)
# pre-PEP8 compatibility
@property
def parserElement(self):
return self.parser_element
@parserElement.setter
def parserElement(self, elem):
self.parser_element = elem
def __str__(self) -> str: def __str__(self) -> str:
if self.pstr: if self.pstr:
if self.loc >= len(self.pstr): if self.loc >= len(self.pstr):
@ -154,14 +183,14 @@ class ParseBaseException(Exception):
foundstr = (", found %r" % found).replace(r"\\", "\\") foundstr = (", found %r" % found).replace(r"\\", "\\")
else: else:
foundstr = "" foundstr = ""
return "{}{} (at char {}), (line:{}, col:{})".format( return f"{self.msg}{foundstr} (at char {self.loc}), (line:{self.lineno}, col:{self.column})"
self.msg, foundstr, self.loc, self.lineno, self.column
)
def __repr__(self): def __repr__(self):
return str(self) return str(self)
def mark_input_line(self, marker_string: str = None, *, markerString=">!<") -> str: def mark_input_line(
self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<"
) -> str:
""" """
Extracts the exception line from the input string, and marks Extracts the exception line from the input string, and marks
the location of the exception with a special symbol. the location of the exception with a special symbol.
@ -214,7 +243,10 @@ class ParseBaseException(Exception):
""" """
return self.explain_exception(self, depth) return self.explain_exception(self, depth)
markInputline = mark_input_line # fmt: off
@replaced_by_pep8(mark_input_line)
def markInputline(self): ...
# fmt: on
class ParseException(ParseBaseException): class ParseException(ParseBaseException):
@ -264,4 +296,4 @@ class RecursiveGrammarException(Exception):
self.parseElementTrace = parseElementList self.parseElementTrace = parseElementList
def __str__(self) -> str: def __str__(self) -> str:
return "RecursiveGrammarException: {}".format(self.parseElementTrace) return f"RecursiveGrammarException: {self.parseElementTrace}"

View file

@ -1,73 +1,22 @@
# helpers.py # helpers.py
import html.entities import html.entities
import re import re
import sys
import typing import typing
from . import __diag__ from . import __diag__
from .core import * from .core import *
from .util import _bslash, _flatten, _escape_regex_range_chars from .util import (
_bslash,
_flatten,
_escape_regex_range_chars,
replaced_by_pep8,
)
# #
# global helpers # global helpers
# #
def delimited_list(
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
min: typing.Optional[int] = None,
max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
"""Helper to define a delimited list of expressions - the delimiter
defaults to ','. By default, the list elements and delimiters can
have intervening whitespace, and comments, but this can be
overridden by passing ``combine=True`` in the constructor. If
``combine`` is set to ``True``, the matching tokens are
returned as a single token string, with the delimiters included;
otherwise, the matching tokens are returned as a list of tokens,
with the delimiters suppressed.
If ``allow_trailing_delim`` is set to True, then the list may end with
a delimiter.
Example::
delimited_list(Word(alphas)).parse_string("aa,bb,cc") # -> ['aa', 'bb', 'cc']
delimited_list(Word(hexnums), delim=':', combine=True).parse_string("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
"""
if isinstance(expr, str_type):
expr = ParserElement._literalStringClass(expr)
dlName = "{expr} [{delim} {expr}]...{end}".format(
expr=str(expr.copy().streamline()),
delim=str(delim),
end=" [{}]".format(str(delim)) if allow_trailing_delim else "",
)
if not combine:
delim = Suppress(delim)
if min is not None:
if min < 1:
raise ValueError("min must be greater than 0")
min -= 1
if max is not None:
if min is not None and max <= min:
raise ValueError("max must be greater than, or equal to min")
max -= 1
delimited_list_expr = expr + (delim + expr)[min, max]
if allow_trailing_delim:
delimited_list_expr += Opt(delim)
if combine:
return Combine(delimited_list_expr).set_name(dlName)
else:
return delimited_list_expr.set_name(dlName)
def counted_array( def counted_array(
expr: ParserElement, expr: ParserElement,
int_expr: typing.Optional[ParserElement] = None, int_expr: typing.Optional[ParserElement] = None,
@ -187,7 +136,7 @@ def match_previous_expr(expr: ParserElement) -> ParserElement:
theseTokens = _flatten(t.as_list()) theseTokens = _flatten(t.as_list())
if theseTokens != matchTokens: if theseTokens != matchTokens:
raise ParseException( raise ParseException(
s, l, "Expected {}, found{}".format(matchTokens, theseTokens) s, l, f"Expected {matchTokens}, found{theseTokens}"
) )
rep.set_parse_action(must_match_these_tokens, callDuringTry=True) rep.set_parse_action(must_match_these_tokens, callDuringTry=True)
@ -218,7 +167,7 @@ def one_of(
- ``caseless`` - treat all literals as caseless - (default= ``False``) - ``caseless`` - treat all literals as caseless - (default= ``False``)
- ``use_regex`` - as an optimization, will - ``use_regex`` - as an optimization, will
generate a :class:`Regex` object; otherwise, will generate generate a :class:`Regex` object; otherwise, will generate
a :class:`MatchFirst` object (if ``caseless=True`` or ``asKeyword=True``, or if a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if
creating a :class:`Regex` raises an exception) - (default= ``True``) creating a :class:`Regex` raises an exception) - (default= ``True``)
- ``as_keyword`` - enforce :class:`Keyword`-style matching on the - ``as_keyword`` - enforce :class:`Keyword`-style matching on the
generated expressions - (default= ``False``) generated expressions - (default= ``False``)
@ -262,6 +211,7 @@ def one_of(
symbols: List[str] = [] symbols: List[str] = []
if isinstance(strs, str_type): if isinstance(strs, str_type):
strs = typing.cast(str, strs)
symbols = strs.split() symbols = strs.split()
elif isinstance(strs, Iterable): elif isinstance(strs, Iterable):
symbols = list(strs) symbols = list(strs)
@ -293,15 +243,13 @@ def one_of(
try: try:
if all(len(sym) == 1 for sym in symbols): if all(len(sym) == 1 for sym in symbols):
# symbols are just single characters, create range regex pattern # symbols are just single characters, create range regex pattern
patt = "[{}]".format( patt = f"[{''.join(_escape_regex_range_chars(sym) for sym in symbols)}]"
"".join(_escape_regex_range_chars(sym) for sym in symbols)
)
else: else:
patt = "|".join(re.escape(sym) for sym in symbols) patt = "|".join(re.escape(sym) for sym in symbols)
# wrap with \b word break markers if defining as keywords # wrap with \b word break markers if defining as keywords
if asKeyword: if asKeyword:
patt = r"\b(?:{})\b".format(patt) patt = rf"\b(?:{patt})\b"
ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols)) ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols))
@ -390,7 +338,7 @@ def original_text_for(
src = "this is test <b> bold <i>text</i> </b> normal text " src = "this is test <b> bold <i>text</i> </b> normal text "
for tag in ("b", "i"): for tag in ("b", "i"):
opener, closer = make_html_tags(tag) opener, closer = make_html_tags(tag)
patt = original_text_for(opener + SkipTo(closer) + closer) patt = original_text_for(opener + ... + closer)
print(patt.search_string(src)[0]) print(patt.search_string(src)[0])
prints:: prints::
@ -426,7 +374,7 @@ def ungroup(expr: ParserElement) -> ParserElement:
def locatedExpr(expr: ParserElement) -> ParserElement: def locatedExpr(expr: ParserElement) -> ParserElement:
""" """
(DEPRECATED - future code should use the Located class) (DEPRECATED - future code should use the :class:`Located` class)
Helper to decorate a returned token with its starting and ending Helper to decorate a returned token with its starting and ending
locations in the input string. locations in the input string.
@ -437,12 +385,12 @@ def locatedExpr(expr: ParserElement) -> ParserElement:
- ``value`` - the actual parsed results - ``value`` - the actual parsed results
Be careful if the input text contains ``<TAB>`` characters, you Be careful if the input text contains ``<TAB>`` characters, you
may want to call :class:`ParserElement.parseWithTabs` may want to call :class:`ParserElement.parse_with_tabs`
Example:: Example::
wd = Word(alphas) wd = Word(alphas)
for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"):
print(match) print(match)
prints:: prints::
@ -471,6 +419,7 @@ def nested_expr(
closing delimiters (``"("`` and ``")"`` are the default). closing delimiters (``"("`` and ``")"`` are the default).
Parameters: Parameters:
- ``opener`` - opening character for a nested list - ``opener`` - opening character for a nested list
(default= ``"("``); can also be a pyparsing expression (default= ``"("``); can also be a pyparsing expression
- ``closer`` - closing character for a nested list - ``closer`` - closing character for a nested list
@ -507,7 +456,7 @@ def nested_expr(
c_function = (decl_data_type("type") c_function = (decl_data_type("type")
+ ident("name") + ident("name")
+ LPAR + Opt(delimited_list(arg), [])("args") + RPAR + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR
+ code_body("body")) + code_body("body"))
c_function.ignore(c_style_comment) c_function.ignore(c_style_comment)
@ -539,6 +488,8 @@ def nested_expr(
raise ValueError("opening and closing strings cannot be the same") raise ValueError("opening and closing strings cannot be the same")
if content is None: if content is None:
if isinstance(opener, str_type) and isinstance(closer, str_type): if isinstance(opener, str_type) and isinstance(closer, str_type):
opener = typing.cast(str, opener)
closer = typing.cast(str, closer)
if len(opener) == 1 and len(closer) == 1: if len(opener) == 1 and len(closer) == 1:
if ignoreExpr is not None: if ignoreExpr is not None:
content = Combine( content = Combine(
@ -695,12 +646,15 @@ common_html_entity = Regex("&(?P<entity>" + "|".join(_htmlEntityMap) + ");").set
) )
def replace_html_entity(t): def replace_html_entity(s, l, t):
"""Helper parser action to replace common HTML entities with their special characters""" """Helper parser action to replace common HTML entities with their special characters"""
return _htmlEntityMap.get(t.entity) return _htmlEntityMap.get(t.entity)
class OpAssoc(Enum): class OpAssoc(Enum):
"""Enumeration of operator associativity
- used in constructing InfixNotationOperatorSpec for :class:`infix_notation`"""
LEFT = 1 LEFT = 1
RIGHT = 2 RIGHT = 2
@ -742,6 +696,7 @@ def infix_notation(
improve your parser performance. improve your parser performance.
Parameters: Parameters:
- ``base_expr`` - expression representing the most basic operand to - ``base_expr`` - expression representing the most basic operand to
be used in the expression be used in the expression
- ``op_list`` - list of tuples, one for each operator precedence level - ``op_list`` - list of tuples, one for each operator precedence level
@ -764,11 +719,11 @@ def infix_notation(
``set_parse_action(*fn)`` ``set_parse_action(*fn)``
(:class:`ParserElement.set_parse_action`) (:class:`ParserElement.set_parse_action`)
- ``lpar`` - expression for matching left-parentheses; if passed as a - ``lpar`` - expression for matching left-parentheses; if passed as a
str, then will be parsed as Suppress(lpar). If lpar is passed as str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as
an expression (such as ``Literal('(')``), then it will be kept in an expression (such as ``Literal('(')``), then it will be kept in
the parsed results, and grouped with them. (default= ``Suppress('(')``) the parsed results, and grouped with them. (default= ``Suppress('(')``)
- ``rpar`` - expression for matching right-parentheses; if passed as a - ``rpar`` - expression for matching right-parentheses; if passed as a
str, then will be parsed as Suppress(rpar). If rpar is passed as str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as
an expression (such as ``Literal(')')``), then it will be kept in an expression (such as ``Literal(')')``), then it will be kept in
the parsed results, and grouped with them. (default= ``Suppress(')')``) the parsed results, and grouped with them. (default= ``Suppress(')')``)
@ -800,9 +755,13 @@ def infix_notation(
(5+3)*6 (5+3)*6
[[[5, '+', 3], '*', 6]] [[[5, '+', 3], '*', 6]]
(5+x)*y
[[[5, '+', 'x'], '*', 'y']]
-2--11 -2--11
[[['-', 2], '-', ['-', 11]]] [[['-', 2], '-', ['-', 11]]]
""" """
# captive version of FollowedBy that does not do parse actions or capture results names # captive version of FollowedBy that does not do parse actions or capture results names
class _FB(FollowedBy): class _FB(FollowedBy):
def parseImpl(self, instring, loc, doActions=True): def parseImpl(self, instring, loc, doActions=True):
@ -823,19 +782,25 @@ def infix_notation(
else: else:
lastExpr = base_expr | (lpar + ret + rpar) lastExpr = base_expr | (lpar + ret + rpar)
arity: int
rightLeftAssoc: opAssoc
pa: typing.Optional[ParseAction]
opExpr1: ParserElement
opExpr2: ParserElement
for i, operDef in enumerate(op_list): for i, operDef in enumerate(op_list):
opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] # type: ignore[assignment]
if isinstance(opExpr, str_type): if isinstance(opExpr, str_type):
opExpr = ParserElement._literalStringClass(opExpr) opExpr = ParserElement._literalStringClass(opExpr)
opExpr = typing.cast(ParserElement, opExpr)
if arity == 3: if arity == 3:
if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2: if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2:
raise ValueError( raise ValueError(
"if numterms=3, opExpr must be a tuple or list of two expressions" "if numterms=3, opExpr must be a tuple or list of two expressions"
) )
opExpr1, opExpr2 = opExpr opExpr1, opExpr2 = opExpr
term_name = "{}{} term".format(opExpr1, opExpr2) term_name = f"{opExpr1}{opExpr2} term"
else: else:
term_name = "{} term".format(opExpr) term_name = f"{opExpr} term"
if not 1 <= arity <= 3: if not 1 <= arity <= 3:
raise ValueError("operator must be unary (1), binary (2), or ternary (3)") raise ValueError("operator must be unary (1), binary (2), or ternary (3)")
@ -843,7 +808,8 @@ def infix_notation(
if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
raise ValueError("operator must indicate right or left associativity") raise ValueError("operator must indicate right or left associativity")
thisExpr: Forward = Forward().set_name(term_name) thisExpr: ParserElement = Forward().set_name(term_name)
thisExpr = typing.cast(Forward, thisExpr)
if rightLeftAssoc is OpAssoc.LEFT: if rightLeftAssoc is OpAssoc.LEFT:
if arity == 1: if arity == 1:
matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
@ -890,7 +856,7 @@ def infix_notation(
def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]):
""" """
(DEPRECATED - use IndentedBlock class instead) (DEPRECATED - use :class:`IndentedBlock` class instead)
Helper method for defining space-delimited indentation blocks, Helper method for defining space-delimited indentation blocks,
such as those used to define block statements in Python source code. such as those used to define block statements in Python source code.
@ -1063,22 +1029,28 @@ _builtin_exprs: List[ParserElement] = [
] ]
# compatibility function, superseded by DelimitedList class
def delimited_list(
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
min: typing.Optional[int] = None,
max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
"""(DEPRECATED - use :class:`DelimitedList` class)"""
return DelimitedList(
expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim
)
# pre-PEP8 compatible names # pre-PEP8 compatible names
delimitedList = delimited_list # fmt: off
countedArray = counted_array
matchPreviousLiteral = match_previous_literal
matchPreviousExpr = match_previous_expr
oneOf = one_of
dictOf = dict_of
originalTextFor = original_text_for
nestedExpr = nested_expr
makeHTMLTags = make_html_tags
makeXMLTags = make_xml_tags
anyOpenTag, anyCloseTag = any_open_tag, any_close_tag
commonHTMLEntity = common_html_entity
replaceHTMLEntity = replace_html_entity
opAssoc = OpAssoc opAssoc = OpAssoc
infixNotation = infix_notation anyOpenTag = any_open_tag
anyCloseTag = any_close_tag
commonHTMLEntity = common_html_entity
cStyleComment = c_style_comment cStyleComment = c_style_comment
htmlComment = html_comment htmlComment = html_comment
restOfLine = rest_of_line restOfLine = rest_of_line
@ -1086,3 +1058,43 @@ dblSlashComment = dbl_slash_comment
cppStyleComment = cpp_style_comment cppStyleComment = cpp_style_comment
javaStyleComment = java_style_comment javaStyleComment = java_style_comment
pythonStyleComment = python_style_comment pythonStyleComment = python_style_comment
@replaced_by_pep8(DelimitedList)
def delimitedList(): ...
@replaced_by_pep8(DelimitedList)
def delimited_list(): ...
@replaced_by_pep8(counted_array)
def countedArray(): ...
@replaced_by_pep8(match_previous_literal)
def matchPreviousLiteral(): ...
@replaced_by_pep8(match_previous_expr)
def matchPreviousExpr(): ...
@replaced_by_pep8(one_of)
def oneOf(): ...
@replaced_by_pep8(dict_of)
def dictOf(): ...
@replaced_by_pep8(original_text_for)
def originalTextFor(): ...
@replaced_by_pep8(nested_expr)
def nestedExpr(): ...
@replaced_by_pep8(make_html_tags)
def makeHTMLTags(): ...
@replaced_by_pep8(make_xml_tags)
def makeXMLTags(): ...
@replaced_by_pep8(replace_html_entity)
def replaceHTMLEntity(): ...
@replaced_by_pep8(infix_notation)
def infixNotation(): ...
# fmt: on

View file

@ -1,18 +1,25 @@
# results.py # results.py
from collections.abc import MutableMapping, Mapping, MutableSequence, Iterator from collections.abc import (
MutableMapping,
Mapping,
MutableSequence,
Iterator,
Sequence,
Container,
)
import pprint import pprint
from weakref import ref as wkref from typing import Tuple, Any, Dict, Set, List
from typing import Tuple, Any
str_type: Tuple[type, ...] = (str, bytes) str_type: Tuple[type, ...] = (str, bytes)
_generator_type = type((_ for _ in ())) _generator_type = type((_ for _ in ()))
class _ParseResultsWithOffset: class _ParseResultsWithOffset:
tup: Tuple["ParseResults", int]
__slots__ = ["tup"] __slots__ = ["tup"]
def __init__(self, p1, p2): def __init__(self, p1: "ParseResults", p2: int):
self.tup = (p1, p2) self.tup: Tuple[ParseResults, int] = (p1, p2)
def __getitem__(self, i): def __getitem__(self, i):
return self.tup[i] return self.tup[i]
@ -47,7 +54,7 @@ class ParseResults:
result = date_str.parse_string("1999/12/31") result = date_str.parse_string("1999/12/31")
def test(s, fn=repr): def test(s, fn=repr):
print("{} -> {}".format(s, fn(eval(s)))) print(f"{s} -> {fn(eval(s))}")
test("list(result)") test("list(result)")
test("result[0]") test("result[0]")
test("result['month']") test("result['month']")
@ -70,27 +77,33 @@ class ParseResults:
- year: '1999' - year: '1999'
""" """
_null_values: Tuple[Any, ...] = (None, [], "", ()) _null_values: Tuple[Any, ...] = (None, [], ())
__slots__ = [ _name: str
_parent: "ParseResults"
_all_names: Set[str]
_modal: bool
_toklist: List[Any]
_tokdict: Dict[str, Any]
__slots__ = (
"_name", "_name",
"_parent", "_parent",
"_all_names", "_all_names",
"_modal", "_modal",
"_toklist", "_toklist",
"_tokdict", "_tokdict",
"__weakref__", )
]
class List(list): class List(list):
""" """
Simple wrapper class to distinguish parsed list results that should be preserved Simple wrapper class to distinguish parsed list results that should be preserved
as actual Python lists, instead of being converted to :class:`ParseResults`: as actual Python lists, instead of being converted to :class:`ParseResults`::
LBRACK, RBRACK = map(pp.Suppress, "[]") LBRACK, RBRACK = map(pp.Suppress, "[]")
element = pp.Forward() element = pp.Forward()
item = ppc.integer item = ppc.integer
element_list = LBRACK + pp.delimited_list(element) + RBRACK element_list = LBRACK + pp.DelimitedList(element) + RBRACK
# add parse actions to convert from ParseResults to actual Python collection types # add parse actions to convert from ParseResults to actual Python collection types
def as_python_list(t): def as_python_list(t):
@ -107,7 +120,7 @@ class ParseResults:
(2,3,4) (2,3,4)
''', post_parse=lambda s, r: (r[0], type(r[0]))) ''', post_parse=lambda s, r: (r[0], type(r[0])))
prints: prints::
100 100
(100, <class 'int'>) (100, <class 'int'>)
@ -127,8 +140,7 @@ class ParseResults:
if not isinstance(contained, list): if not isinstance(contained, list):
raise TypeError( raise TypeError(
"{} may only be constructed with a list," f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}"
" not {}".format(cls.__name__, type(contained).__name__)
) )
return list.__new__(cls) return list.__new__(cls)
@ -159,6 +171,7 @@ class ParseResults:
def __init__( def __init__(
self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance
): ):
self._tokdict: Dict[str, _ParseResultsWithOffset]
self._modal = modal self._modal = modal
if name is not None and name != "": if name is not None and name != "":
if isinstance(name, int): if isinstance(name, int):
@ -210,7 +223,7 @@ class ParseResults:
] ]
sub = v sub = v
if isinstance(sub, ParseResults): if isinstance(sub, ParseResults):
sub._parent = wkref(self) sub._parent = self
def __delitem__(self, i): def __delitem__(self, i):
if isinstance(i, (int, slice)): if isinstance(i, (int, slice)):
@ -263,7 +276,7 @@ class ParseResults:
""" """
Since ``keys()`` returns an iterator, this method is helpful in bypassing Since ``keys()`` returns an iterator, this method is helpful in bypassing
code that looks for the existence of any defined results names.""" code that looks for the existence of any defined results names."""
return bool(self._tokdict) return not not self._tokdict
def pop(self, *args, **kwargs): def pop(self, *args, **kwargs):
""" """
@ -311,9 +324,7 @@ class ParseResults:
if k == "default": if k == "default":
args = (args[0], v) args = (args[0], v)
else: else:
raise TypeError( raise TypeError(f"pop() got an unexpected keyword argument {k!r}")
"pop() got an unexpected keyword argument {!r}".format(k)
)
if isinstance(args[0], int) or len(args) == 1 or args[0] in self: if isinstance(args[0], int) or len(args) == 1 or args[0] in self:
index = args[0] index = args[0]
ret = self[index] ret = self[index]
@ -423,12 +434,15 @@ class ParseResults:
raise AttributeError(name) raise AttributeError(name)
return "" return ""
def __add__(self, other) -> "ParseResults": def __add__(self, other: "ParseResults") -> "ParseResults":
ret = self.copy() ret = self.copy()
ret += other ret += other
return ret return ret
def __iadd__(self, other) -> "ParseResults": def __iadd__(self, other: "ParseResults") -> "ParseResults":
if not other:
return self
if other._tokdict: if other._tokdict:
offset = len(self._toklist) offset = len(self._toklist)
addoffset = lambda a: offset if a < 0 else a + offset addoffset = lambda a: offset if a < 0 else a + offset
@ -441,7 +455,7 @@ class ParseResults:
for k, v in otherdictitems: for k, v in otherdictitems:
self[k] = v self[k] = v
if isinstance(v[0], ParseResults): if isinstance(v[0], ParseResults):
v[0]._parent = wkref(self) v[0]._parent = self
self._toklist += other._toklist self._toklist += other._toklist
self._all_names |= other._all_names self._all_names |= other._all_names
@ -456,7 +470,7 @@ class ParseResults:
return other + self return other + self
def __repr__(self) -> str: def __repr__(self) -> str:
return "{}({!r}, {})".format(type(self).__name__, self._toklist, self.as_dict()) return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})"
def __str__(self) -> str: def __str__(self) -> str:
return ( return (
@ -532,7 +546,10 @@ class ParseResults:
def copy(self) -> "ParseResults": def copy(self) -> "ParseResults":
""" """
Returns a new copy of a :class:`ParseResults` object. Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults`
items contained within the source are shared with the copy. Use
:class:`ParseResults.deepcopy()` to create a copy with its own separate
content values.
""" """
ret = ParseResults(self._toklist) ret = ParseResults(self._toklist)
ret._tokdict = self._tokdict.copy() ret._tokdict = self._tokdict.copy()
@ -541,6 +558,27 @@ class ParseResults:
ret._name = self._name ret._name = self._name
return ret return ret
def deepcopy(self) -> "ParseResults":
"""
Returns a new deep copy of a :class:`ParseResults` object.
"""
ret = self.copy()
# replace values with copies if they are of known mutable types
for i, obj in enumerate(self._toklist):
if isinstance(obj, ParseResults):
self._toklist[i] = obj.deepcopy()
elif isinstance(obj, (str, bytes)):
pass
elif isinstance(obj, MutableMapping):
self._toklist[i] = dest = type(obj)()
for k, v in obj.items():
dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v
elif isinstance(obj, Container):
self._toklist[i] = type(obj)(
v.deepcopy() if isinstance(v, ParseResults) else v for v in obj
)
return ret
def get_name(self): def get_name(self):
r""" r"""
Returns the results name for this token expression. Useful when several Returns the results name for this token expression. Useful when several
@ -569,20 +607,17 @@ class ParseResults:
if self._name: if self._name:
return self._name return self._name
elif self._parent: elif self._parent:
par = self._parent() par: "ParseResults" = self._parent
parent_tokdict_items = par._tokdict.items()
def find_in_parent(sub):
return next( return next(
( (
k k
for k, vlist in par._tokdict.items() for k, vlist in parent_tokdict_items
for v, loc in vlist for v, loc in vlist
if sub is v if v is self
), ),
None, None,
) )
return find_in_parent(self) if par else None
elif ( elif (
len(self) == 1 len(self) == 1
and len(self._tokdict) == 1 and len(self._tokdict) == 1
@ -623,7 +658,7 @@ class ParseResults:
for k, v in items: for k, v in items:
if out: if out:
out.append(NL) out.append(NL)
out.append("{}{}- {}: ".format(indent, (" " * _depth), k)) out.append(f"{indent}{(' ' * _depth)}- {k}: ")
if isinstance(v, ParseResults): if isinstance(v, ParseResults):
if v: if v:
out.append( out.append(
@ -685,7 +720,7 @@ class ParseResults:
num = Word(nums) num = Word(nums)
func = Forward() func = Forward()
term = ident | num | Group('(' + func + ')') term = ident | num | Group('(' + func + ')')
func <<= ident + Group(Optional(delimited_list(term))) func <<= ident + Group(Optional(DelimitedList(term)))
result = func.parse_string("fna a,b,(fnb c,d,200),100") result = func.parse_string("fna a,b,(fnb c,d,200),100")
result.pprint(width=40) result.pprint(width=40)
@ -705,7 +740,7 @@ class ParseResults:
self._toklist, self._toklist,
( (
self._tokdict.copy(), self._tokdict.copy(),
self._parent is not None and self._parent() or None, None,
self._all_names, self._all_names,
self._name, self._name,
), ),
@ -714,9 +749,6 @@ class ParseResults:
def __setstate__(self, state): def __setstate__(self, state):
self._toklist, (self._tokdict, par, inAccumNames, self._name) = state self._toklist, (self._tokdict, par, inAccumNames, self._name) = state
self._all_names = set(inAccumNames) self._all_names = set(inAccumNames)
if par is not None:
self._parent = wkref(par)
else:
self._parent = None self._parent = None
def __getnewargs__(self): def __getnewargs__(self):
@ -738,6 +770,7 @@ class ParseResults:
iter(obj) iter(obj)
except Exception: except Exception:
return False return False
# str's are iterable, but in pyparsing, we don't want to iterate over them
else: else:
return not isinstance(obj, str_type) return not isinstance(obj, str_type)
@ -752,8 +785,11 @@ class ParseResults:
return ret return ret
asList = as_list asList = as_list
"""Deprecated - use :class:`as_list`"""
asDict = as_dict asDict = as_dict
"""Deprecated - use :class:`as_dict`"""
getName = get_name getName = get_name
"""Deprecated - use :class:`get_name`"""
MutableMapping.register(ParseResults) MutableMapping.register(ParseResults)

View file

@ -222,7 +222,7 @@ class pyparsing_test:
) )
else: else:
# warning here maybe? # warning here maybe?
print("no validation for {!r}".format(test_string)) print(f"no validation for {test_string!r}")
# do this last, in case some specific test results can be reported instead # do this last, in case some specific test results can be reported instead
self.assertTrue( self.assertTrue(
@ -265,15 +265,18 @@ class pyparsing_test:
if expand_tabs: if expand_tabs:
s = s.expandtabs() s = s.expandtabs()
if mark_control is not None: if mark_control is not None:
mark_control = typing.cast(str, mark_control)
if mark_control == "unicode": if mark_control == "unicode":
tbl = str.maketrans( transtable_map = {
{c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))} c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433))
| {127: 0x2421} }
) transtable_map[127] = 0x2421
tbl = str.maketrans(transtable_map)
eol_mark = "" eol_mark = ""
else: else:
ord_mark_control = ord(mark_control)
tbl = str.maketrans( tbl = str.maketrans(
{c: mark_control for c in list(range(0, 32)) + [127]} {c: ord_mark_control for c in list(range(0, 32)) + [127]}
) )
s = s.translate(tbl) s = s.translate(tbl)
if mark_spaces is not None and mark_spaces != " ": if mark_spaces is not None and mark_spaces != " ":
@ -303,7 +306,7 @@ class pyparsing_test:
header0 = ( header0 = (
lead lead
+ "".join( + "".join(
"{}{}".format(" " * 99, (i + 1) % 100) f"{' ' * 99}{(i + 1) % 100}"
for i in range(max(max_line_len // 100, 1)) for i in range(max(max_line_len // 100, 1))
) )
+ "\n" + "\n"
@ -313,10 +316,7 @@ class pyparsing_test:
header1 = ( header1 = (
header0 header0
+ lead + lead
+ "".join( + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10)))
" {}".format((i + 1) % 10)
for i in range(-(-max_line_len // 10))
)
+ "\n" + "\n"
) )
header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n" header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n"
@ -324,7 +324,7 @@ class pyparsing_test:
header1 header1
+ header2 + header2
+ "\n".join( + "\n".join(
"{:{}d}:{}{}".format(i, lineno_width, line, eol_mark) f"{i:{lineno_width}d}:{line}{eol_mark}"
for i, line in enumerate(s_lines, start=start_line) for i, line in enumerate(s_lines, start=start_line)
) )
+ "\n" + "\n"

View file

@ -64,27 +64,27 @@ class unicode_set:
@_lazyclassproperty @_lazyclassproperty
def printables(cls): def printables(cls):
"all non-whitespace characters in this range" """all non-whitespace characters in this range"""
return "".join(filterfalse(str.isspace, cls._chars_for_ranges)) return "".join(filterfalse(str.isspace, cls._chars_for_ranges))
@_lazyclassproperty @_lazyclassproperty
def alphas(cls): def alphas(cls):
"all alphabetic characters in this range" """all alphabetic characters in this range"""
return "".join(filter(str.isalpha, cls._chars_for_ranges)) return "".join(filter(str.isalpha, cls._chars_for_ranges))
@_lazyclassproperty @_lazyclassproperty
def nums(cls): def nums(cls):
"all numeric digit characters in this range" """all numeric digit characters in this range"""
return "".join(filter(str.isdigit, cls._chars_for_ranges)) return "".join(filter(str.isdigit, cls._chars_for_ranges))
@_lazyclassproperty @_lazyclassproperty
def alphanums(cls): def alphanums(cls):
"all alphanumeric characters in this range" """all alphanumeric characters in this range"""
return cls.alphas + cls.nums return cls.alphas + cls.nums
@_lazyclassproperty @_lazyclassproperty
def identchars(cls): def identchars(cls):
"all characters in this range that are valid identifier characters, plus underscore '_'" """all characters in this range that are valid identifier characters, plus underscore '_'"""
return "".join( return "".join(
sorted( sorted(
set( set(
@ -100,13 +100,13 @@ class unicode_set:
def identbodychars(cls): def identbodychars(cls):
""" """
all characters in this range that are valid identifier body characters, all characters in this range that are valid identifier body characters,
plus the digits 0-9 plus the digits 0-9, and · (Unicode MIDDLE DOT)
""" """
return "".join( return "".join(
sorted( sorted(
set( set(
cls.identchars cls.identchars
+ "0123456789" + "0123456789·"
+ "".join( + "".join(
[c for c in cls._chars_for_ranges if ("_" + c).isidentifier()] [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()]
) )
@ -114,6 +114,16 @@ class unicode_set:
) )
) )
@_lazyclassproperty
def identifier(cls):
"""
a pyparsing Word expression for an identifier using this range's definitions for
identchars and identbodychars
"""
from pyparsing import Word
return Word(cls.identchars, cls.identbodychars)
class pyparsing_unicode(unicode_set): class pyparsing_unicode(unicode_set):
""" """
@ -128,32 +138,32 @@ class pyparsing_unicode(unicode_set):
] ]
class BasicMultilingualPlane(unicode_set): class BasicMultilingualPlane(unicode_set):
"Unicode set for the Basic Multilingual Plane" """Unicode set for the Basic Multilingual Plane"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0020, 0xFFFF), (0x0020, 0xFFFF),
] ]
class Latin1(unicode_set): class Latin1(unicode_set):
"Unicode set for Latin-1 Unicode Character Range" """Unicode set for Latin-1 Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0020, 0x007E), (0x0020, 0x007E),
(0x00A0, 0x00FF), (0x00A0, 0x00FF),
] ]
class LatinA(unicode_set): class LatinA(unicode_set):
"Unicode set for Latin-A Unicode Character Range" """Unicode set for Latin-A Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0100, 0x017F), (0x0100, 0x017F),
] ]
class LatinB(unicode_set): class LatinB(unicode_set):
"Unicode set for Latin-B Unicode Character Range" """Unicode set for Latin-B Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0180, 0x024F), (0x0180, 0x024F),
] ]
class Greek(unicode_set): class Greek(unicode_set):
"Unicode set for Greek Unicode Character Ranges" """Unicode set for Greek Unicode Character Ranges"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0342, 0x0345), (0x0342, 0x0345),
(0x0370, 0x0377), (0x0370, 0x0377),
@ -193,7 +203,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Cyrillic(unicode_set): class Cyrillic(unicode_set):
"Unicode set for Cyrillic Unicode Character Range" """Unicode set for Cyrillic Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0400, 0x052F), (0x0400, 0x052F),
(0x1C80, 0x1C88), (0x1C80, 0x1C88),
@ -206,7 +216,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Chinese(unicode_set): class Chinese(unicode_set):
"Unicode set for Chinese Unicode Character Range" """Unicode set for Chinese Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x2E80, 0x2E99), (0x2E80, 0x2E99),
(0x2E9B, 0x2EF3), (0x2E9B, 0x2EF3),
@ -229,8 +239,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Japanese(unicode_set): class Japanese(unicode_set):
"Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges" """Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges"""
_ranges: UnicodeRangeList = []
class Kanji(unicode_set): class Kanji(unicode_set):
"Unicode set for Kanji Unicode Character Range" "Unicode set for Kanji Unicode Character Range"
@ -240,7 +249,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Hiragana(unicode_set): class Hiragana(unicode_set):
"Unicode set for Hiragana Unicode Character Range" """Unicode set for Hiragana Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x3041, 0x3096), (0x3041, 0x3096),
(0x3099, 0x30A0), (0x3099, 0x30A0),
@ -252,7 +261,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Katakana(unicode_set): class Katakana(unicode_set):
"Unicode set for Katakana Unicode Character Range" """Unicode set for Katakana Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x3099, 0x309C), (0x3099, 0x309C),
(0x30A0, 0x30FF), (0x30A0, 0x30FF),
@ -265,8 +274,18 @@ class pyparsing_unicode(unicode_set):
(0x1F213,), (0x1F213,),
] ]
漢字 = Kanji
カタカナ = Katakana
ひらがな = Hiragana
_ranges = (
Kanji._ranges
+ Hiragana._ranges
+ Katakana._ranges
)
class Hangul(unicode_set): class Hangul(unicode_set):
"Unicode set for Hangul (Korean) Unicode Character Range" """Unicode set for Hangul (Korean) Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x1100, 0x11FF), (0x1100, 0x11FF),
(0x302E, 0x302F), (0x302E, 0x302F),
@ -288,17 +307,17 @@ class pyparsing_unicode(unicode_set):
Korean = Hangul Korean = Hangul
class CJK(Chinese, Japanese, Hangul): class CJK(Chinese, Japanese, Hangul):
"Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range" """Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"""
class Thai(unicode_set): class Thai(unicode_set):
"Unicode set for Thai Unicode Character Range" """Unicode set for Thai Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0E01, 0x0E3A), (0x0E01, 0x0E3A),
(0x0E3F, 0x0E5B) (0x0E3F, 0x0E5B)
] ]
class Arabic(unicode_set): class Arabic(unicode_set):
"Unicode set for Arabic Unicode Character Range" """Unicode set for Arabic Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0600, 0x061B), (0x0600, 0x061B),
(0x061E, 0x06FF), (0x061E, 0x06FF),
@ -306,7 +325,7 @@ class pyparsing_unicode(unicode_set):
] ]
class Hebrew(unicode_set): class Hebrew(unicode_set):
"Unicode set for Hebrew Unicode Character Range" """Unicode set for Hebrew Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0591, 0x05C7), (0x0591, 0x05C7),
(0x05D0, 0x05EA), (0x05D0, 0x05EA),
@ -320,33 +339,23 @@ class pyparsing_unicode(unicode_set):
] ]
class Devanagari(unicode_set): class Devanagari(unicode_set):
"Unicode set for Devanagari Unicode Character Range" """Unicode set for Devanagari Unicode Character Range"""
_ranges: UnicodeRangeList = [ _ranges: UnicodeRangeList = [
(0x0900, 0x097F), (0x0900, 0x097F),
(0xA8E0, 0xA8FF) (0xA8E0, 0xA8FF)
] ]
# fmt: on BMP = BasicMultilingualPlane
pyparsing_unicode.Japanese._ranges = (
pyparsing_unicode.Japanese.Kanji._ranges
+ pyparsing_unicode.Japanese.Hiragana._ranges
+ pyparsing_unicode.Japanese.Katakana._ranges
)
pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
# add language identifiers using language Unicode # add language identifiers using language Unicode
pyparsing_unicode.العربية = pyparsing_unicode.Arabic العربية = Arabic
pyparsing_unicode.中文 = pyparsing_unicode.Chinese 中文 = Chinese
pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic кириллица = Cyrillic
pyparsing_unicode.Ελληνικά = pyparsing_unicode.Greek Ελληνικά = Greek
pyparsing_unicode.עִברִית = pyparsing_unicode.Hebrew עִברִית = Hebrew
pyparsing_unicode.日本語 = pyparsing_unicode.Japanese 日本語 = Japanese
pyparsing_unicode.Japanese.漢字 = pyparsing_unicode.Japanese.Kanji 한국어 = Korean
pyparsing_unicode.Japanese.カタカナ = pyparsing_unicode.Japanese.Katakana ไทย = Thai
pyparsing_unicode.Japanese.ひらがな = pyparsing_unicode.Japanese.Hiragana वनगर = Devanagari
pyparsing_unicode.한국어 = pyparsing_unicode.Korean
pyparsing_unicode.ไทย = pyparsing_unicode.Thai # fmt: on
pyparsing_unicode.वनगर = pyparsing_unicode.Devanagari

View file

@ -1,12 +1,14 @@
# util.py # util.py
import inspect
import warnings import warnings
import types import types
import collections import collections
import itertools import itertools
from functools import lru_cache from functools import lru_cache, wraps
from typing import List, Union, Iterable from typing import Callable, List, Union, Iterable, TypeVar, cast
_bslash = chr(92) _bslash = chr(92)
C = TypeVar("C", bound=Callable)
class __config_flags: class __config_flags:
@ -20,18 +22,15 @@ class __config_flags:
def _set(cls, dname, value): def _set(cls, dname, value):
if dname in cls._fixed_names: if dname in cls._fixed_names:
warnings.warn( warnings.warn(
"{}.{} {} is {} and cannot be overridden".format( f"{cls.__name__}.{dname} {cls._type_desc} is {str(getattr(cls, dname)).upper()}"
cls.__name__, f" and cannot be overridden",
dname, stacklevel=3,
cls._type_desc,
str(getattr(cls, dname)).upper(),
)
) )
return return
if dname in cls._all_names: if dname in cls._all_names:
setattr(cls, dname, value) setattr(cls, dname, value)
else: else:
raise ValueError("no such {} {!r}".format(cls._type_desc, dname)) raise ValueError(f"no such {cls._type_desc} {dname!r}")
enable = classmethod(lambda cls, name: cls._set(name, True)) enable = classmethod(lambda cls, name: cls._set(name, True))
disable = classmethod(lambda cls, name: cls._set(name, False)) disable = classmethod(lambda cls, name: cls._set(name, False))
@ -45,7 +44,7 @@ def col(loc: int, strg: str) -> int:
Note: the default parsing behavior is to expand tabs in the input string Note: the default parsing behavior is to expand tabs in the input string
before starting the parsing process. See before starting the parsing process. See
:class:`ParserElement.parseString` for more :class:`ParserElement.parse_string` for more
information on parsing strings containing ``<TAB>`` s, and suggested information on parsing strings containing ``<TAB>`` s, and suggested
methods to maintain a consistent view of the parsed string, the parse methods to maintain a consistent view of the parsed string, the parse
location, and line and column positions within the parsed string. location, and line and column positions within the parsed string.
@ -60,7 +59,7 @@ def lineno(loc: int, strg: str) -> int:
The first line is number 1. The first line is number 1.
Note - the default parsing behavior is to expand tabs in the input string Note - the default parsing behavior is to expand tabs in the input string
before starting the parsing process. See :class:`ParserElement.parseString` before starting the parsing process. See :class:`ParserElement.parse_string`
for more information on parsing strings containing ``<TAB>`` s, and for more information on parsing strings containing ``<TAB>`` s, and
suggested methods to maintain a consistent view of the parsed string, the suggested methods to maintain a consistent view of the parsed string, the
parse location, and line and column positions within the parsed string. parse location, and line and column positions within the parsed string.
@ -102,19 +101,24 @@ class _UnboundedCache:
class _FifoCache: class _FifoCache:
def __init__(self, size): def __init__(self, size):
self.not_in_cache = not_in_cache = object() self.not_in_cache = not_in_cache = object()
cache = collections.OrderedDict() cache = {}
keyring = [object()] * size
cache_get = cache.get cache_get = cache.get
cache_pop = cache.pop
keyiter = itertools.cycle(range(size))
def get(_, key): def get(_, key):
return cache_get(key, not_in_cache) return cache_get(key, not_in_cache)
def set_(_, key, value): def set_(_, key, value):
cache[key] = value cache[key] = value
while len(cache) > size: i = next(keyiter)
cache.popitem(last=False) cache_pop(keyring[i], None)
keyring[i] = key
def clear(_): def clear(_):
cache.clear() cache.clear()
keyring[:] = [object()] * size
self.size = size self.size = size
self.get = types.MethodType(get, self) self.get = types.MethodType(get, self)
@ -189,9 +193,9 @@ def _collapse_string_to_ranges(
is_consecutive.value = next(is_consecutive.counter) is_consecutive.value = next(is_consecutive.counter)
return is_consecutive.value return is_consecutive.value
is_consecutive.prev = 0 is_consecutive.prev = 0 # type: ignore [attr-defined]
is_consecutive.counter = itertools.count() is_consecutive.counter = itertools.count() # type: ignore [attr-defined]
is_consecutive.value = -1 is_consecutive.value = -1 # type: ignore [attr-defined]
def escape_re_range_char(c): def escape_re_range_char(c):
return "\\" + c if c in r"\^-][" else c return "\\" + c if c in r"\^-][" else c
@ -215,9 +219,7 @@ def _collapse_string_to_ranges(
else: else:
sep = "" if ord(last) == ord(first) + 1 else "-" sep = "" if ord(last) == ord(first) + 1 else "-"
ret.append( ret.append(
"{}{}{}".format( f"{escape_re_range_char(first)}{sep}{escape_re_range_char(last)}"
escape_re_range_char(first), sep, escape_re_range_char(last)
)
) )
else: else:
ret = [escape_re_range_char(c) for c in s] ret = [escape_re_range_char(c) for c in s]
@ -233,3 +235,50 @@ def _flatten(ll: list) -> list:
else: else:
ret.append(i) ret.append(i)
return ret return ret
def _make_synonym_function(compat_name: str, fn: C) -> C:
# In a future version, uncomment the code in the internal _inner() functions
# to begin emitting DeprecationWarnings.
# Unwrap staticmethod/classmethod
fn = getattr(fn, "__func__", fn)
# (Presence of 'self' arg in signature is used by explain_exception() methods, so we take
# some extra steps to add it if present in decorated function.)
if "self" == list(inspect.signature(fn).parameters)[0]:
@wraps(fn)
def _inner(self, *args, **kwargs):
# warnings.warn(
# f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3
# )
return fn(self, *args, **kwargs)
else:
@wraps(fn)
def _inner(*args, **kwargs):
# warnings.warn(
# f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3
# )
return fn(*args, **kwargs)
_inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`"""
_inner.__name__ = compat_name
_inner.__annotations__ = fn.__annotations__
if isinstance(fn, types.FunctionType):
_inner.__kwdefaults__ = fn.__kwdefaults__
elif isinstance(fn, type) and hasattr(fn, "__init__"):
_inner.__kwdefaults__ = fn.__init__.__kwdefaults__
else:
_inner.__kwdefaults__ = None
_inner.__qualname__ = fn.__qualname__
return cast(C, _inner)
def replaced_by_pep8(fn: C) -> Callable[[Callable], C]:
"""
Decorator for pre-PEP8 compatibility synonyms, to link them to the new function.
"""
return lambda other: _make_synonym_function(other.__name__, fn)

View file

@ -32,7 +32,7 @@ plexapi==4.13.4
portend==3.2.0 portend==3.2.0
profilehooks==1.12.0 profilehooks==1.12.0
PyJWT==2.8.0 PyJWT==2.8.0
pyparsing==3.0.9 pyparsing==3.1.1
python-dateutil==2.8.2 python-dateutil==2.8.2
python-twitter==3.5 python-twitter==3.5
pytz==2023.3 pytz==2023.3