[bumpversion]
-current_version = 63.4.1
+current_version = 63.4.2
commit = True
tag = True
+v63.4.2
+-------
+
+
+Misc
+^^^^
+* #3453: Bump vendored version of :pypi:`pyparsing` to 3.0.9.
+* #3481: Add warning for potential ``install_requires`` and ``extras_require``
+ misconfiguration in ``setup.cfg``
+* #3487: Modified ``pyproject.toml`` validation exception handling to
+ make relevant debugging information easier to spot.
+
+
v63.4.1
-------
^^^^^^^^^^^^^^^^
* #3421: Drop setuptools' support for installing an entrypoint extra requirements at load time:
- the functionality has been broken since v60.8.0.
- - the mechanism to do so is deprecated (`fetch_build_eggs`).
+ - the mechanism to do so is deprecated (``fetch_build_eggs``).
- that use case (e.g. a custom command class entrypoint) is covered by making sure the necessary build requirements are declared.
Documentation changes
('py:exc', 'LibError'), # undocumented
('py:exc', 'LinkError'), # undocumented
('py:exc', 'PreprocessError'), # undocumented
+ ('py:exc', 'setuptools.errors.PlatformError'), # sphinx cannot find it
('py:func', 'distutils.CCompiler.new_compiler'), # undocumented
# undocumented:
('py:func', 'distutils.dist.DistributionMetadata.read_pkg_file'),
to validate the ``setup()`` argument, if it's supplied. The ``Distribution``
object will have the initial value of the attribute set to ``None``, and the
validation function will only be called if the ``setup()`` call sets it to
-a non-None value. Here's an example validation function::
+a non-``None`` value. Here's an example validation function::
def assert_bool(dist, attr, value):
"""Verify that value is True, False, 0, or 1"""
if bool(value) != value:
- raise DistutilsSetupError(
+ raise SetupError(
"%r must be a boolean value (got %r)" % (attr,value)
)
Your function should accept three arguments: the ``Distribution`` object,
the attribute name, and the attribute value. It should raise a
``SetupError`` (from the ``setuptools.errors`` module) if the argument
-is invalid. Remember, your function will only be called with non-None values,
-and the default value of arguments defined this way is always None. So, your
+is invalid. Remember, your function will only be called with non-``None`` values,
+and the default value of arguments defined this way is always ``None``. So, your
commands should always be prepared for the possibility that the attribute will
be ``None`` when they access it later.
Customizing Distribution Options
--------------------------------
-Plugins may wish to extend or alter the options on a Distribution object to
+Plugins may wish to extend or alter the options on a ``Distribution`` object to
suit the purposes of that project. For example, a tool that infers the
``Distribution.version`` from SCM-metadata may need to hook into the
option finalization. To enable this feature, Setuptools offers an entry
-point "setuptools.finalize_distribution_options". That entry point must
-be a callable taking one argument (the Distribution instance).
+point ``setuptools.finalize_distribution_options``. That entry point must
+be a callable taking one argument (the ``Distribution`` instance).
If the callable has an ``.order`` property, that value will be used to
determine the order in which the hook is called. Lower numbers are called
[tool.setuptools.packages]
find = {} # Scanning implicit namespaces is active by default
# OR
- find = {namespace = false} # Disable implicit namespaces
+ find = {namespaces = false} # Disable implicit namespaces
Finding simple packages
.. note:: New in 61.0.0
.. important::
- For the time being, ``pip`` still might require a ``setup.py`` file
- to support :doc:`editable installs <pip:cli/pip_install>`.
+ For the time being [#pep660-status]_, ``pip`` still might require a ``setup.py`` file
+ to support :doc:`editable installs <pip:cli/pip_install>` [#setupcfg-caveats]_.
A simple script will suffice, for example:
.. rubric:: Notes
+.. [#pep660-status] Editable install without ``setup.py`` will be supported in
+ future versions of ``setuptools``. Check https://github.com/pypa/setuptools/issues/2816 for detail.
+
+.. [#setupcfg-caveats] ``pip`` may allow editable install only with ``pyproject.toml``
+ and ``setup.cfg``. However, this behavior may not be consistent over various build
+ tools. Having a ``setup.py`` is still recommended if you rely on one of these tools.
+
.. [#entry-points] Dynamic ``scripts`` and ``gui-scripts`` are a special case.
When resolving these metadata keys, ``setuptools`` will look for
``tool.setuptool.dynamic.entry-points``, and use the values of the
# OR
[tool.setuptools.packages.find]
+ # All the following settings are optional:
where = ["src"] # ["."] by default
include = ["mypackage*"] # ["*"] by default
exclude = ["mypackage.tests*"] # empty by default
[options]
packages = find: # OR `find_namespace:` if you want to use namespaces
- [options.packages.find] # (always `find` even if `find_namespace:` was used before)
- # This section is optional
- # Each entry in this section is optional, and if not specified, the default values are:
- # `where=.`, `include=*` and `exclude=` (empty).
- include=mypackage*
- exclude=mypackage.tests*
+ [options.packages.find] # (always `find` even if `find_namespace:` was used before)
+ # This section is optional as well as each of the following options:
+ where=src # . by default
+ include=mypackage* # * by default
+ exclude=mypackage.tests* # empty by default
.. tab:: setup.py [#setup.py]_
setup(
# ...
packages=find_packages(
- where='.',
- include=['mypackage*'], # ["*"] by default
+ # All keyword arguments below are optional:
+ where='src', # '.' by default
+ include=['mypackage*'], # ['*'] by default
exclude=['mypackage.tests'], # empty by default
),
# ...
)
When you pass the above information, alongside other necessary information,
-``setuptools`` walks through the directory specified in ``where`` (omitted
-here as the package resides in the current directory) and filters the packages
+``setuptools`` walks through the directory specified in ``where`` (defaults to ``.``) and filters the packages
it can find following the ``include`` patterns (defaults to ``*``), then it removes
-those that match the ``exclude`` patterns and returns a list of Python packages.
+those that match the ``exclude`` patterns (defaults to empty) and returns a list of Python packages.
For more details and advanced use, go to :ref:`package_discovery`.
--- /dev/null
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+Metadata-Version: 2.1
+Name: pyparsing
+Version: 3.0.9
+Summary: pyparsing module - Classes and methods to define and execute parsing grammars
+Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
+Requires-Python: >=3.6.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Requires-Dist: railroad-diagrams ; extra == "diagrams"
+Requires-Dist: jinja2 ; extra == "diagrams"
+Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
+Provides-Extra: diagrams
+
+PyParsing -- A Python Parsing Module
+====================================
+
+|Build Status| |Coverage|
+
+Introduction
+============
+
+The pyparsing module is an alternative approach to creating and
+executing simple grammars, vs. the traditional lex/yacc approach, or the
+use of regular expressions. The pyparsing module provides a library of
+classes that client code uses to construct the grammar directly in
+Python code.
+
+*[Since first writing this description of pyparsing in late 2003, this
+technique for developing parsers has become more widespread, under the
+name Parsing Expression Grammars - PEGs. See more information on PEGs*
+`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
+*.]*
+
+Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
+``"salutation, addressee!"``):
+
+.. code:: python
+
+ from pyparsing import Word, alphas
+ greet = Word(alphas) + "," + Word(alphas) + "!"
+ hello = "Hello, World!"
+ print(hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+ Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the
+self-explanatory class names, and the use of '+', '|' and '^' operator
+definitions.
+
+The parsed results returned from ``parseString()`` is a collection of type
+``ParseResults``, which can be accessed as a
+nested list, a dictionary, or an object with named attributes.
+
+The pyparsing module handles some of the problems that are typically
+vexing when writing text parsers:
+
+- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
+- quoted strings
+- embedded comments
+
+The examples directory includes a simple SQL parser, simple CORBA IDL
+parser, a config file parser, a chemical formula parser, and a four-
+function algebraic notation parser, among many others.
+
+Documentation
+=============
+
+There are many examples in the online docstrings of the classes
+and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
+documentation resources and project info are listed in the online
+`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
+entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
+
+License
+=======
+
+MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
+
+History
+=======
+
+See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
+
+.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
+ :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
+.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/pyparsing/pyparsing
+
--- /dev/null
+pyparsing-3.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pyparsing-3.0.9.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
+pyparsing-3.0.9.dist-info/METADATA,sha256=h_fpm9rwvgZsE8v5YNF4IAo-IpaFWCOfUEm5MMByIiM,4207
+pyparsing-3.0.9.dist-info/RECORD,,
+pyparsing-3.0.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing-3.0.9.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
+pyparsing/__init__.py,sha256=52QH3lgPbJhba0estckoGPHRH8JvQSSCGoWiEn2m0bU,9159
+pyparsing/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/__pycache__/actions.cpython-38.pyc,,
+pyparsing/__pycache__/common.cpython-38.pyc,,
+pyparsing/__pycache__/core.cpython-38.pyc,,
+pyparsing/__pycache__/exceptions.cpython-38.pyc,,
+pyparsing/__pycache__/helpers.cpython-38.pyc,,
+pyparsing/__pycache__/results.cpython-38.pyc,,
+pyparsing/__pycache__/testing.cpython-38.pyc,,
+pyparsing/__pycache__/unicode.cpython-38.pyc,,
+pyparsing/__pycache__/util.cpython-38.pyc,,
+pyparsing/actions.py,sha256=wU9i32e0y1ymxKE3OUwSHO-SFIrt1h_wv6Ws0GQjpNU,6426
+pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
+pyparsing/core.py,sha256=u8GptQE_H6wMkl8OZhxeK1aAPIDXXNgwdShORBwBVS4,213310
+pyparsing/diagram/__init__.py,sha256=f_EfxahqrdkRVahmTwLJXkZ9EEDKNd-O7lBbpJYlE1g,23668
+pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/exceptions.py,sha256=3LbSafD32NYb1Tzt85GHNkhEAU1eZkTtNSk24cPMemo,9023
+pyparsing/helpers.py,sha256=QpUOjW0-psvueMwWb9bQpU2noqKCv98_wnw1VSzSdVo,39129
+pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing/results.py,sha256=HgNvWVXBdQP-Q6PtJfoCEeOJk2nwEvG-2KVKC5sGA30,25341
+pyparsing/testing.py,sha256=7tu4Abp4uSeJV0N_yEPRmmNUhpd18ZQP3CrX41DM814,13402
+pyparsing/unicode.py,sha256=fwuhMj30SQ165Cv7HJpu-rSxGbRm93kN9L4Ei7VGc1Y,10787
+pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
--- /dev/null
+Wheel-Version: 1.0
+Generator: flit 3.6.0
+Root-Is-Purelib: true
+Tag: py3-none-any
)
-__version_info__ = version_info(3, 0, 8, "final", 0)
-__version_time__ = "09 Apr 2022 23:29 UTC"
+__version_info__ = version_info(3, 0, 9, "final", 0)
+__version_time__ = "05 May 2022 07:02 UTC"
__version__ = __version_info__.__version__
__versionTime__ = __version_time__
__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
term = na | num
- OneOrMore(term).parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
+ term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
"""
return lambda s, l, t: [repl_str]
# core.py
#
import os
+import typing
from typing import (
- Optional as OptionalType,
- Iterable as IterableType,
NamedTuple,
Union,
Callable,
List,
TextIO,
Set,
- Dict as DictType,
Sequence,
)
from abc import ABC, abstractmethod
def _should_enable_warnings(
- cmd_line_warn_options: IterableType[str], warn_env_var: OptionalType[str]
+ cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
) -> bool:
enable = bool(warn_env_var)
for warn_opt in cmd_line_warn_options:
DEFAULT_WHITE_CHARS: str = " \n\t\r"
verbose_stacktrace: bool = False
- _literalStringClass: OptionalType[type] = None
+ _literalStringClass: typing.Optional[type] = None
@staticmethod
def set_default_whitespace_chars(chars: str) -> None:
Example::
# default whitespace chars are space, <TAB> and newline
- OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
# change to just treat newline as significant
ParserElement.set_default_whitespace_chars(" \t")
- OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def']
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def']
"""
ParserElement.DEFAULT_WHITE_CHARS = chars
ParserElement._literalStringClass = cls
class DebugActions(NamedTuple):
- debug_try: OptionalType[DebugStartAction]
- debug_match: OptionalType[DebugSuccessAction]
- debug_fail: OptionalType[DebugExceptionAction]
+ debug_try: typing.Optional[DebugStartAction]
+ debug_match: typing.Optional[DebugSuccessAction]
+ debug_fail: typing.Optional[DebugExceptionAction]
def __init__(self, savelist: bool = False):
self.parseAction: List[ParseAction] = list()
- self.failAction: OptionalType[ParseFailAction] = None
+ self.failAction: typing.Optional[ParseFailAction] = None
self.customName = None
self._defaultName = None
self.resultsName = None
integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
- print(OneOrMore(integerK | integerM | integer).parse_string("5K 100 640K 256M"))
+ print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
prints::
# cache for left-recursion in Forward references
recursion_lock = RLock()
- recursion_memos: DictType[
+ recursion_memos: typing.Dict[
Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
] = {}
@staticmethod
def enable_left_recursion(
- cache_size_limit: OptionalType[int] = None, *, force=False
+ cache_size_limit: typing.Optional[int] = None, *, force=False
) -> None:
"""
Enables "bounded recursion" parsing, which allows for both direct and indirect
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
patt.parse_string('ablaj /* comment */ lskjd')
# -> ['ablaj']
# turn on debugging for wd
wd.set_debug()
- OneOrMore(term).parse_string("abc 123 xyz 890")
+ term[1, ...].parse_string("abc 123 xyz 890")
prints::
self,
tests: Union[str, List[str]],
parse_all: bool = True,
- comment: OptionalType[Union["ParserElement", str]] = "#",
+ comment: typing.Optional[Union["ParserElement", str]] = "#",
full_dump: bool = True,
print_results: bool = True,
failure_tests: bool = False,
post_parse: Callable[[str, ParseResults], str] = None,
- file: OptionalType[TextIO] = None,
+ file: typing.Optional[TextIO] = None,
with_line_numbers: bool = False,
*,
parseAll: bool = True,
def __init__(
self,
match_string: str = "",
- ident_chars: OptionalType[str] = None,
+ ident_chars: typing.Optional[str] = None,
caseless: bool = False,
*,
matchString: str = "",
- identChars: OptionalType[str] = None,
+ identChars: typing.Optional[str] = None,
):
super().__init__()
identChars = identChars or ident_chars
Example::
- OneOrMore(CaselessLiteral("CMD")).parse_string("cmd CMD Cmd10")
+ CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD', 'CMD']
(Contrast with example for :class:`CaselessKeyword`.)
Example::
- OneOrMore(CaselessKeyword("CMD")).parse_string("cmd CMD Cmd10")
+ CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD']
(Contrast with example for :class:`CaselessLiteral`.)
def __init__(
self,
match_string: str = "",
- ident_chars: OptionalType[str] = None,
+ ident_chars: typing.Optional[str] = None,
*,
matchString: str = "",
- identChars: OptionalType[str] = None,
+ identChars: typing.Optional[str] = None,
):
identChars = identChars or ident_chars
match_string = matchString or match_string
def __init__(
self,
init_chars: str = "",
- body_chars: OptionalType[str] = None,
+ body_chars: typing.Optional[str] = None,
min: int = 1,
max: int = 0,
exact: int = 0,
as_keyword: bool = False,
- exclude_chars: OptionalType[str] = None,
+ exclude_chars: typing.Optional[str] = None,
*,
- initChars: OptionalType[str] = None,
- bodyChars: OptionalType[str] = None,
+ initChars: typing.Optional[str] = None,
+ bodyChars: typing.Optional[str] = None,
asKeyword: bool = False,
- excludeChars: OptionalType[str] = None,
+ excludeChars: typing.Optional[str] = None,
):
initChars = initChars or init_chars
bodyChars = bodyChars or body_chars
self,
charset: str,
as_keyword: bool = False,
- exclude_chars: OptionalType[str] = None,
+ exclude_chars: typing.Optional[str] = None,
*,
asKeyword: bool = False,
- excludeChars: OptionalType[str] = None,
+ excludeChars: typing.Optional[str] = None,
):
asKeyword = asKeyword or as_keyword
excludeChars = excludeChars or exclude_chars
def __init__(
self,
quote_char: str = "",
- esc_char: OptionalType[str] = None,
- esc_quote: OptionalType[str] = None,
+ esc_char: typing.Optional[str] = None,
+ esc_quote: typing.Optional[str] = None,
multiline: bool = False,
unquote_results: bool = True,
- end_quote_char: OptionalType[str] = None,
+ end_quote_char: typing.Optional[str] = None,
convert_whitespace_escapes: bool = True,
*,
quoteChar: str = "",
- escChar: OptionalType[str] = None,
- escQuote: OptionalType[str] = None,
+ escChar: typing.Optional[str] = None,
+ escQuote: typing.Optional[str] = None,
unquoteResults: bool = True,
- endQuoteChar: OptionalType[str] = None,
+ endQuoteChar: typing.Optional[str] = None,
convertWhitespaceEscapes: bool = True,
):
super().__init__()
post-processing parsed tokens.
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(savelist)
self.exprs: List[ParserElement]
if isinstance(exprs, _generatorType):
Example::
integer = Word(nums)
- name_expr = OneOrMore(Word(alphas))
+ name_expr = Word(alphas)[1, ...]
expr = And([integer("id"), name_expr("name"), integer("age")])
# more easily written as:
def _generateDefaultName(self):
return "-"
- def __init__(self, exprs_arg: IterableType[ParserElement], savelist: bool = True):
+ def __init__(
+ self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
+ ):
exprs: List[ParserElement] = list(exprs_arg)
if exprs and Ellipsis in exprs:
tmp = []
[['123'], ['3.1416'], ['789']]
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
- size: 20
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = True):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
label = data_word + FollowedBy(':')
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
- OneOrMore(attr_expr).parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
+ attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
prints::
"""
def __init__(
- self, expr: Union[ParserElement, str], retreat: OptionalType[int] = None
+ self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
):
super().__init__(expr)
self.expr = self.expr().leave_whitespace()
# very crude boolean expression - to support parenthesis groups and
# operation hierarchy, use infix_notation
- boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term)
+ boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
# integers that are followed by "." are actually floats
integer = Word(nums) + ~Char(".")
def __init__(
self,
expr: ParserElement,
- stop_on: OptionalType[Union[ParserElement, str]] = None,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
*,
- stopOn: OptionalType[Union[ParserElement, str]] = None,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
):
super().__init__(expr)
stopOn = stopOn or stop_on
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
text = "shape: SQUARE posn: upper left color: BLACK"
- OneOrMore(attr_expr).parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+ attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
# use stop_on attribute for OneOrMore to avoid reading label string as part of the data
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
def __init__(
self,
expr: ParserElement,
- stop_on: OptionalType[Union[ParserElement, str]] = None,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
*,
- stopOn: OptionalType[Union[ParserElement, str]] = None,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
):
super().__init__(expr, stopOn=stopOn or stop_on)
self.mayReturnEmpty = True
other: Union[ParserElement, str],
include: bool = False,
ignore: bool = None,
- fail_on: OptionalType[Union[ParserElement, str]] = None,
+ fail_on: typing.Optional[Union[ParserElement, str]] = None,
*,
failOn: Union[ParserElement, str] = None,
):
parser created using ``Forward``.
"""
- def __init__(self, other: OptionalType[Union[ParserElement, str]] = None):
+ def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
self.caller_frame = traceback.extract_stack(limit=2)[0]
super().__init__(other, savelist=False)
self.lshift_line = None
join_string: str = "",
adjacent: bool = True,
*,
- joinString: OptionalType[str] = None,
+ joinString: typing.Optional[str] = None,
):
super().__init__(expr)
joinString = joinString if joinString is not None else join_string
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
# print attributes as plain groups
- print(OneOrMore(attr_expr).parse_string(text).dump())
+ print(attr_expr[1, ...].parse_string(text).dump())
- # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
- result = Dict(OneOrMore(Group(attr_expr))).parse_string(text)
+ # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
+ result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
print(result.dump())
# access named fields as dict entries, or output as dict
source = "a, b, c,d"
wd = Word(alphas)
- wd_list1 = wd + ZeroOrMore(',' + wd)
+ wd_list1 = wd + (',' + wd)[...]
print(wd_list1.parse_string(source))
# often, delimiters that are useful during parsing are just in the
# way afterward - use Suppress to keep them out of the parsed output
- wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+ wd_list2 = wd + (Suppress(',') + wd)[...]
print(wd_list2.parse_string(source))
# Skipped text (using '...') can be suppressed as well
def remove_duplicate_chars(tokens):
return ''.join(sorted(set(''.join(tokens))))
- wds = OneOrMore(wd).set_parse_action(remove_duplicate_chars)
+ wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
prints::
Example (compare the last to example in :class:`ParserElement.transform_string`::
- hex_ints = OneOrMore(Word(hexnums)).set_parse_action(token_map(int, 16))
+ hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
hex_ints.run_tests('''
00 11 22 aa FF 0a 0d 1a
''')
upperword = Word(alphas).set_parse_action(token_map(str.upper))
- OneOrMore(upperword).run_tests('''
+ upperword[1, ...].run_tests('''
my kingdom for a horse
''')
wd = Word(alphas).set_parse_action(token_map(str.title))
- OneOrMore(wd).set_parse_action(' '.join).run_tests('''
+ wd[1, ...].set_parse_action(' '.join).run_tests('''
now is the winter of our discontent made glorious summer by this sun of york
''')
# build list of built-in expressions, for future reference if a global default value
# gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
# backward compatibility names
tokenMap = token_map
import railroad
import pyparsing
-from pkg_resources import resource_filename
+import typing
from typing import (
List,
- Optional,
NamedTuple,
Generic,
TypeVar,
import inspect
-with open(resource_filename(__name__, "template.jinja2"), encoding="utf-8") as fp:
- template = Template(fp.read())
+jinja2_template_source = """\
+<!DOCTYPE html>
+<html>
+<head>
+ {% if not head %}
+ <style type="text/css">
+ .railroad-heading {
+ font-family: monospace;
+ }
+ </style>
+ {% else %}
+ {{ head | safe }}
+ {% endif %}
+</head>
+<body>
+{{ body | safe }}
+{% for diagram in diagrams %}
+ <div class="railroad-group">
+ <h1 class="railroad-heading">{{ diagram.title }}</h1>
+ <div class="railroad-description">{{ diagram.text }}</div>
+ <div class="railroad-svg">
+ {{ diagram.svg }}
+ </div>
+ </div>
+{% endfor %}
+</body>
+</html>
+"""
+
+template = Template(jinja2_template_source)
# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
NamedDiagram = NamedTuple(
"NamedDiagram",
- [("name", str), ("diagram", Optional[railroad.DiagramItem]), ("index", int)],
+ [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
)
"""
A simple structure for associating a name with a railroad diagram
"""
data = []
for diagram in diagrams:
+ if diagram.diagram is None:
+ continue
io = StringIO()
diagram.diagram.writeSvg(io.write)
title = diagram.name
def to_railroad(
element: pyparsing.ParserElement,
- diagram_kwargs: Optional[dict] = None,
+ diagram_kwargs: typing.Optional[dict] = None,
vertical: int = 3,
show_results_names: bool = False,
show_groups: bool = False,
parent: EditablePartial,
number: int,
name: str = None,
- parent_index: Optional[int] = None,
+ parent_index: typing.Optional[int] = None,
):
#: The pyparsing element that this represents
self.element: pyparsing.ParserElement = element
#: The name of the element
- self.name: str = name
+ self.name: typing.Optional[str] = name
#: The output Railroad element in an unconverted state
self.converted: EditablePartial = converted
#: The parent Railroad element, which we store so that we can extract this if it's duplicated
#: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
self.number: int = number
#: The index of this inside its parent
- self.parent_index: Optional[int] = parent_index
+ self.parent_index: typing.Optional[int] = parent_index
#: If true, we should extract this out into a subdiagram
self.extract: bool = False
#: If true, all of this element's children have been filled out
Stores some state that persists between recursions into the element tree
"""
- def __init__(self, diagram_kwargs: Optional[dict] = None):
+ def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
#: A dictionary mapping ParserElements to state relating to them
self._element_diagram_states: Dict[int, ElementState] = {}
#: A dictionary mapping ParserElement IDs to subdiagrams generated from them
def _inner(
element: pyparsing.ParserElement,
- parent: Optional[EditablePartial],
+ parent: typing.Optional[EditablePartial],
lookup: ConverterState = None,
vertical: int = None,
index: int = 0,
name_hint: str = None,
show_results_names: bool = False,
show_groups: bool = False,
- ) -> Optional[EditablePartial]:
+ ) -> typing.Optional[EditablePartial]:
ret = fn(
element,
@_apply_diagram_item_enhancements
def _to_diagram_element(
element: pyparsing.ParserElement,
- parent: Optional[EditablePartial],
+ parent: typing.Optional[EditablePartial],
lookup: ConverterState = None,
vertical: int = None,
index: int = 0,
name_hint: str = None,
show_results_names: bool = False,
show_groups: bool = False,
-) -> Optional[EditablePartial]:
+) -> typing.Optional[EditablePartial]:
"""
Recursively converts a PyParsing Element to a railroad Element
:param lookup: The shared converter state that keeps track of useful things
else:
ret = EditablePartial.from_call(railroad.Group, label="", item="")
elif isinstance(element, pyparsing.TokenConverter):
- ret = EditablePartial.from_call(AnnotatedItem, label=type(element).__name__.lower(), item="")
+ ret = EditablePartial.from_call(
+ AnnotatedItem, label=type(element).__name__.lower(), item=""
+ )
elif isinstance(element, pyparsing.Opt):
ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore):
+++ /dev/null
-<!DOCTYPE html>
-<html>
-<head>
- {% if not head %}
- <style type="text/css">
- .railroad-heading {
- font-family: monospace;
- }
- </style>
- {% else %}
- {{ hear | safe }}
- {% endif %}
-</head>
-<body>
-{{ body | safe }}
-{% for diagram in diagrams %}
- <div class="railroad-group">
- <h1 class="railroad-heading">{{ diagram.title }}</h1>
- <div class="railroad-description">{{ diagram.text }}</div>
- <div class="railroad-svg">
- {{ diagram.svg }}
- </div>
- </div>
-{% endfor %}
-</body>
-</html>
import re
import sys
-from typing import Optional
+import typing
from .util import col, line, lineno, _collapse_string_to_ranges
from .unicode import pyparsing_unicode as ppu
self,
pstr: str,
loc: int = 0,
- msg: Optional[str] = None,
+ msg: typing.Optional[str] = None,
elem=None,
):
self.loc = loc
# helpers.py
import html.entities
import re
+import typing
from . import __diag__
from .core import *
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
- min: OptionalType[int] = None,
- max: OptionalType[int] = None,
+ min: typing.Optional[int] = None,
+ max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
def counted_array(
expr: ParserElement,
- int_expr: OptionalType[ParserElement] = None,
+ int_expr: typing.Optional[ParserElement] = None,
*,
- intExpr: OptionalType[ParserElement] = None,
+ intExpr: typing.Optional[ParserElement] = None,
) -> ParserElement:
"""Helper to define a counted list of expressions.
def one_of(
- strs: Union[IterableType[str], str],
+ strs: Union[typing.Iterable[str], str],
caseless: bool = False,
use_regex: bool = True,
as_keyword: bool = False,
text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
- print(OneOrMore(attr_expr).parse_string(text).dump())
+ print(attr_expr[1, ...].parse_string(text).dump())
attr_label = label
attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
def nested_expr(
opener: Union[str, ParserElement] = "(",
closer: Union[str, ParserElement] = ")",
- content: OptionalType[ParserElement] = None,
+ content: typing.Optional[ParserElement] = None,
ignore_expr: ParserElement = quoted_string(),
*,
ignoreExpr: ParserElement = quoted_string(),
return _makeTags(tag_str, True)
+any_open_tag: ParserElement
+any_close_tag: ParserElement
any_open_tag, any_close_tag = make_html_tags(
Word(alphas, alphanums + "_:").set_name("any tag")
)
InfixNotationOperatorArgType,
int,
OpAssoc,
- OptionalType[ParseAction],
+ typing.Optional[ParseAction],
],
Tuple[
InfixNotationOperatorArgType,
if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
raise ValueError("operator must indicate right or left associativity")
- thisExpr = Forward().set_name(term_name)
+ thisExpr: Forward = Forward().set_name(term_name)
if rightLeftAssoc is OpAssoc.LEFT:
if arity == 1:
matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
assignment = Group(identifier + "=" + rvalue)
stmt << (funcDef | assignment | identifier)
- module_body = OneOrMore(stmt)
+ module_body = stmt[1, ...]
parseTree = module_body.parseString(data)
parseTree.pprint()
# build list of built-in expressions, for future reference if a global default value
# gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
# pre-PEP8 compatible names
print(numlist.parse_string("0 123 321")) # -> ['123', '321']
label = Word(alphas)
- patt = label("LABEL") + OneOrMore(Word(nums))
+ patt = label("LABEL") + Word(nums)[1, ...]
print(patt.parse_string("AAB 123 321").dump())
# Use pop() in a parse action to remove named result (note that corresponding value is not
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
# use a parse action to append the reverse of the matched strings, to make a palindrome
def make_palindrome(tokens):
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
result = patt.parse_string("sldkj lsdkj sldkj")
# even though the result prints in string-like form, it is actually a pyparsing ParseResults
print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
user_data = (Group(house_number_expr)("house_number")
| Group(ssn_expr)("ssn")
| Group(integer)("age"))
- user_info = OneOrMore(user_data)
+ user_info = user_data[1, ...]
result = user_info.parse_string("22 111-22-3333 #221B")
for item in result:
# testing.py
from contextlib import contextmanager
-from typing import Optional
+import typing
from .core import (
ParserElement,
@staticmethod
def with_line_numbers(
s: str,
- start_line: Optional[int] = None,
- end_line: Optional[int] = None,
+ start_line: typing.Optional[int] = None,
+ end_line: typing.Optional[int] = None,
expand_tabs: bool = True,
eol_mark: str = "|",
- mark_spaces: Optional[str] = None,
- mark_control: Optional[str] = None,
+ mark_spaces: typing.Optional[str] = None,
+ mark_control: typing.Optional[str] = None,
) -> str:
"""
Helpful method for debugging a parser - prints a string with line and column numbers.
A namespace class for defining common language unicode_sets.
"""
- _ranges: UnicodeRangeList = [(32, sys.maxunicode)]
+ # fmt: off
+
+ # define ranges in language character sets
+ _ranges: UnicodeRangeList = [
+ (0x0020, sys.maxunicode),
+ ]
+
+ class BasicMultilingualPlane(unicode_set):
+ "Unicode set for the Basic Multilingual Plane"
+ _ranges: UnicodeRangeList = [
+ (0x0020, 0xFFFF),
+ ]
class Latin1(unicode_set):
"Unicode set for Latin-1 Unicode Character Range"
class CJK(Chinese, Japanese, Hangul):
"Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
- pass
class Thai(unicode_set):
"Unicode set for Thai Unicode Character Range"
- _ranges: UnicodeRangeList = [(0x0E01, 0x0E3A), (0x0E3F, 0x0E5B)]
+ _ranges: UnicodeRangeList = [
+ (0x0E01, 0x0E3A),
+ (0x0E3F, 0x0E5B)
+ ]
class Arabic(unicode_set):
"Unicode set for Arabic Unicode Character Range"
class Devanagari(unicode_set):
"Unicode set for Devanagari Unicode Character Range"
- _ranges: UnicodeRangeList = [(0x0900, 0x097F), (0xA8E0, 0xA8FF)]
+ _ranges: UnicodeRangeList = [
+ (0x0900, 0x097F),
+ (0xA8E0, 0xA8FF)
+ ]
+
+ # fmt: on
pyparsing_unicode.Japanese._ranges = (
+ pyparsing_unicode.Japanese.Katakana._ranges
)
-# define ranges in language character sets
+pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
+
+# add language identifiers using language Unicode
pyparsing_unicode.العربية = pyparsing_unicode.Arabic
pyparsing_unicode.中文 = pyparsing_unicode.Chinese
pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
packaging==21.3
-pyparsing==3.0.8
+pyparsing==3.0.9
appdirs==1.4.3
jaraco.text==3.7.0
# required for jaraco.text on older Pythons
[metadata]
name = setuptools
-version = 63.4.1
+version = 63.4.2
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
+++ /dev/null
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+++ /dev/null
-Metadata-Version: 2.1
-Name: pyparsing
-Version: 3.0.8
-Summary: pyparsing module - Classes and methods to define and execute parsing grammars
-Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
-Requires-Python: >=3.6.8
-Description-Content-Type: text/x-rst
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: Information Technology
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Typing :: Typed
-Requires-Dist: railroad-diagrams ; extra == "diagrams"
-Requires-Dist: jinja2 ; extra == "diagrams"
-Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
-Provides-Extra: diagrams
-
-PyParsing -- A Python Parsing Module
-====================================
-
-|Build Status| |Coverage|
-
-Introduction
-============
-
-The pyparsing module is an alternative approach to creating and
-executing simple grammars, vs. the traditional lex/yacc approach, or the
-use of regular expressions. The pyparsing module provides a library of
-classes that client code uses to construct the grammar directly in
-Python code.
-
-*[Since first writing this description of pyparsing in late 2003, this
-technique for developing parsers has become more widespread, under the
-name Parsing Expression Grammars - PEGs. See more information on PEGs*
-`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
-*.]*
-
-Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
-``"salutation, addressee!"``):
-
-.. code:: python
-
- from pyparsing import Word, alphas
- greet = Word(alphas) + "," + Word(alphas) + "!"
- hello = "Hello, World!"
- print(hello, "->", greet.parseString(hello))
-
-The program outputs the following::
-
- Hello, World! -> ['Hello', ',', 'World', '!']
-
-The Python representation of the grammar is quite readable, owing to the
-self-explanatory class names, and the use of '+', '|' and '^' operator
-definitions.
-
-The parsed results returned from ``parseString()`` is a collection of type
-``ParseResults``, which can be accessed as a
-nested list, a dictionary, or an object with named attributes.
-
-The pyparsing module handles some of the problems that are typically
-vexing when writing text parsers:
-
-- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
-- quoted strings
-- embedded comments
-
-The examples directory includes a simple SQL parser, simple CORBA IDL
-parser, a config file parser, a chemical formula parser, and a four-
-function algebraic notation parser, among many others.
-
-Documentation
-=============
-
-There are many examples in the online docstrings of the classes
-and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
-documentation resources and project info are listed in the online
-`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
-entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
-
-License
-=======
-
-MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
-
-History
-=======
-
-See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
-
-.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
- :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
-.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/pyparsing/pyparsing
-
+++ /dev/null
-pyparsing-3.0.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-pyparsing-3.0.8.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
-pyparsing-3.0.8.dist-info/METADATA,sha256=dEvZBGz3Owm5LYEaqDeKb6e3ZgOrF48WaCI_PG1n5BE,4207
-pyparsing-3.0.8.dist-info/RECORD,,
-pyparsing-3.0.8.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-pyparsing-3.0.8.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
-pyparsing/__init__.py,sha256=EMa1HCuq9HJhEDR8fUThu2gD0nl6Cs8FFEWZZ0eRCM8,9159
-pyparsing/__pycache__/__init__.cpython-38.pyc,,
-pyparsing/__pycache__/actions.cpython-38.pyc,,
-pyparsing/__pycache__/common.cpython-38.pyc,,
-pyparsing/__pycache__/core.cpython-38.pyc,,
-pyparsing/__pycache__/exceptions.cpython-38.pyc,,
-pyparsing/__pycache__/helpers.cpython-38.pyc,,
-pyparsing/__pycache__/results.cpython-38.pyc,,
-pyparsing/__pycache__/testing.cpython-38.pyc,,
-pyparsing/__pycache__/unicode.cpython-38.pyc,,
-pyparsing/__pycache__/util.cpython-38.pyc,,
-pyparsing/actions.py,sha256=60v7mETOBzc01YPH_qQD5isavgcSJpAfIKpzgjM3vaU,6429
-pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
-pyparsing/core.py,sha256=zBzGw5vcSd58pB1QkYpY6O_XCcHVKX_nH5xglRx_L-M,213278
-pyparsing/diagram/__init__.py,sha256=oU_UEh6O5voKSFjUdq462_mpmURLOfUIsmWvxi1qgTQ,23003
-pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
-pyparsing/diagram/template.jinja2,sha256=SfQ8SLktSBqI5W1DGcUVH1vdflRD6x2sQBApxrcNg7s,589
-pyparsing/exceptions.py,sha256=H4D9gqMavqmAFSsdrU_J6bO-jA-T-A7yvtXWZpooIUA,9030
-pyparsing/helpers.py,sha256=EyjpgDOc3ivwRsU4VXxAWdgIs5gaqMDaLWcwRh5mqxc,39007
-pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-pyparsing/results.py,sha256=Hd6FAAh5sF8zGXpwsamdVqFUblIwyQf0FH0t7FCb1OY,25353
-pyparsing/testing.py,sha256=szs8AKZREZMhL0y0vsMfaTVAnpqPHetg6VKJBNmc4QY,13388
-pyparsing/unicode.py,sha256=IR-ioeGY29cZ49tG8Ts7ITPWWNP5G2DcZs58oa8zn44,10381
-pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
+++ /dev/null
-Wheel-Version: 1.0
-Generator: flit 3.6.0
-Root-Is-Purelib: true
-Tag: py3-none-any
--- /dev/null
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+Metadata-Version: 2.1
+Name: pyparsing
+Version: 3.0.9
+Summary: pyparsing module - Classes and methods to define and execute parsing grammars
+Author-email: Paul McGuire <ptmcg.gm+pyparsing@gmail.com>
+Requires-Python: >=3.6.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Typing :: Typed
+Requires-Dist: railroad-diagrams ; extra == "diagrams"
+Requires-Dist: jinja2 ; extra == "diagrams"
+Project-URL: Homepage, https://github.com/pyparsing/pyparsing/
+Provides-Extra: diagrams
+
+PyParsing -- A Python Parsing Module
+====================================
+
+|Build Status| |Coverage|
+
+Introduction
+============
+
+The pyparsing module is an alternative approach to creating and
+executing simple grammars, vs. the traditional lex/yacc approach, or the
+use of regular expressions. The pyparsing module provides a library of
+classes that client code uses to construct the grammar directly in
+Python code.
+
+*[Since first writing this description of pyparsing in late 2003, this
+technique for developing parsers has become more widespread, under the
+name Parsing Expression Grammars - PEGs. See more information on PEGs*
+`here <https://en.wikipedia.org/wiki/Parsing_expression_grammar>`__
+*.]*
+
+Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
+``"salutation, addressee!"``):
+
+.. code:: python
+
+ from pyparsing import Word, alphas
+ greet = Word(alphas) + "," + Word(alphas) + "!"
+ hello = "Hello, World!"
+ print(hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+ Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the
+self-explanatory class names, and the use of '+', '|' and '^' operator
+definitions.
+
+The parsed results returned from ``parseString()`` is a collection of type
+``ParseResults``, which can be accessed as a
+nested list, a dictionary, or an object with named attributes.
+
+The pyparsing module handles some of the problems that are typically
+vexing when writing text parsers:
+
+- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
+- quoted strings
+- embedded comments
+
+The examples directory includes a simple SQL parser, simple CORBA IDL
+parser, a config file parser, a chemical formula parser, and a four-
+function algebraic notation parser, among many others.
+
+Documentation
+=============
+
+There are many examples in the online docstrings of the classes
+and methods in pyparsing. You can find them compiled into `online docs <https://pyparsing-docs.readthedocs.io/en/latest/>`__. Additional
+documentation resources and project info are listed in the online
+`GitHub wiki <https://github.com/pyparsing/pyparsing/wiki>`__. An
+entire directory of examples can be found `here <https://github.com/pyparsing/pyparsing/tree/master/examples>`__.
+
+License
+=======
+
+MIT License. See header of the `pyparsing.py <https://github.com/pyparsing/pyparsing/blob/master/pyparsing/__init__.py#L1-L23>`__ file.
+
+History
+=======
+
+See `CHANGES <https://github.com/pyparsing/pyparsing/blob/master/CHANGES>`__ file.
+
+.. |Build Status| image:: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml/badge.svg
+ :target: https://github.com/pyparsing/pyparsing/actions/workflows/ci.yml
+.. |Coverage| image:: https://codecov.io/gh/pyparsing/pyparsing/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/pyparsing/pyparsing
+
--- /dev/null
+pyparsing-3.0.9.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pyparsing-3.0.9.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
+pyparsing-3.0.9.dist-info/METADATA,sha256=h_fpm9rwvgZsE8v5YNF4IAo-IpaFWCOfUEm5MMByIiM,4207
+pyparsing-3.0.9.dist-info/RECORD,,
+pyparsing-3.0.9.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing-3.0.9.dist-info/WHEEL,sha256=jPMR_Dzkc4X4icQtmz81lnNY_kAsfog7ry7qoRvYLXw,81
+pyparsing/__init__.py,sha256=52QH3lgPbJhba0estckoGPHRH8JvQSSCGoWiEn2m0bU,9159
+pyparsing/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/__pycache__/actions.cpython-38.pyc,,
+pyparsing/__pycache__/common.cpython-38.pyc,,
+pyparsing/__pycache__/core.cpython-38.pyc,,
+pyparsing/__pycache__/exceptions.cpython-38.pyc,,
+pyparsing/__pycache__/helpers.cpython-38.pyc,,
+pyparsing/__pycache__/results.cpython-38.pyc,,
+pyparsing/__pycache__/testing.cpython-38.pyc,,
+pyparsing/__pycache__/unicode.cpython-38.pyc,,
+pyparsing/__pycache__/util.cpython-38.pyc,,
+pyparsing/actions.py,sha256=wU9i32e0y1ymxKE3OUwSHO-SFIrt1h_wv6Ws0GQjpNU,6426
+pyparsing/common.py,sha256=lFL97ooIeR75CmW5hjURZqwDCTgruqltcTCZ-ulLO2Q,12936
+pyparsing/core.py,sha256=u8GptQE_H6wMkl8OZhxeK1aAPIDXXNgwdShORBwBVS4,213310
+pyparsing/diagram/__init__.py,sha256=f_EfxahqrdkRVahmTwLJXkZ9EEDKNd-O7lBbpJYlE1g,23668
+pyparsing/diagram/__pycache__/__init__.cpython-38.pyc,,
+pyparsing/exceptions.py,sha256=3LbSafD32NYb1Tzt85GHNkhEAU1eZkTtNSk24cPMemo,9023
+pyparsing/helpers.py,sha256=QpUOjW0-psvueMwWb9bQpU2noqKCv98_wnw1VSzSdVo,39129
+pyparsing/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pyparsing/results.py,sha256=HgNvWVXBdQP-Q6PtJfoCEeOJk2nwEvG-2KVKC5sGA30,25341
+pyparsing/testing.py,sha256=7tu4Abp4uSeJV0N_yEPRmmNUhpd18ZQP3CrX41DM814,13402
+pyparsing/unicode.py,sha256=fwuhMj30SQ165Cv7HJpu-rSxGbRm93kN9L4Ei7VGc1Y,10787
+pyparsing/util.py,sha256=kq772O5YSeXOSdP-M31EWpbH_ayj7BMHImBYo9xPD5M,6805
--- /dev/null
+Wheel-Version: 1.0
+Generator: flit 3.6.0
+Root-Is-Purelib: true
+Tag: py3-none-any
)
-__version_info__ = version_info(3, 0, 8, "final", 0)
-__version_time__ = "09 Apr 2022 23:29 UTC"
+__version_info__ = version_info(3, 0, 9, "final", 0)
+__version_time__ = "05 May 2022 07:02 UTC"
__version__ = __version_info__.__version__
__versionTime__ = __version_time__
__author__ = "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>"
na = one_of("N/A NA").set_parse_action(replace_with(math.nan))
term = na | num
- OneOrMore(term).parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
+ term[1, ...].parse_string("324 234 N/A 234") # -> [324, 234, nan, 234]
"""
return lambda s, l, t: [repl_str]
# core.py
#
import os
+import typing
from typing import (
- Optional as OptionalType,
- Iterable as IterableType,
NamedTuple,
Union,
Callable,
List,
TextIO,
Set,
- Dict as DictType,
Sequence,
)
from abc import ABC, abstractmethod
def _should_enable_warnings(
- cmd_line_warn_options: IterableType[str], warn_env_var: OptionalType[str]
+ cmd_line_warn_options: typing.Iterable[str], warn_env_var: typing.Optional[str]
) -> bool:
enable = bool(warn_env_var)
for warn_opt in cmd_line_warn_options:
DEFAULT_WHITE_CHARS: str = " \n\t\r"
verbose_stacktrace: bool = False
- _literalStringClass: OptionalType[type] = None
+ _literalStringClass: typing.Optional[type] = None
@staticmethod
def set_default_whitespace_chars(chars: str) -> None:
Example::
# default whitespace chars are space, <TAB> and newline
- OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl']
# change to just treat newline as significant
ParserElement.set_default_whitespace_chars(" \t")
- OneOrMore(Word(alphas)).parse_string("abc def\nghi jkl") # -> ['abc', 'def']
+ Word(alphas)[1, ...].parse_string("abc def\nghi jkl") # -> ['abc', 'def']
"""
ParserElement.DEFAULT_WHITE_CHARS = chars
ParserElement._literalStringClass = cls
class DebugActions(NamedTuple):
- debug_try: OptionalType[DebugStartAction]
- debug_match: OptionalType[DebugSuccessAction]
- debug_fail: OptionalType[DebugExceptionAction]
+ debug_try: typing.Optional[DebugStartAction]
+ debug_match: typing.Optional[DebugSuccessAction]
+ debug_fail: typing.Optional[DebugExceptionAction]
def __init__(self, savelist: bool = False):
self.parseAction: List[ParseAction] = list()
- self.failAction: OptionalType[ParseFailAction] = None
+ self.failAction: typing.Optional[ParseFailAction] = None
self.customName = None
self._defaultName = None
self.resultsName = None
integerK = integer.copy().add_parse_action(lambda toks: toks[0] * 1024) + Suppress("K")
integerM = integer.copy().add_parse_action(lambda toks: toks[0] * 1024 * 1024) + Suppress("M")
- print(OneOrMore(integerK | integerM | integer).parse_string("5K 100 640K 256M"))
+ print((integerK | integerM | integer)[1, ...].parse_string("5K 100 640K 256M"))
prints::
# cache for left-recursion in Forward references
recursion_lock = RLock()
- recursion_memos: DictType[
+ recursion_memos: typing.Dict[
Tuple[int, "Forward", bool], Tuple[int, Union[ParseResults, Exception]]
] = {}
@staticmethod
def enable_left_recursion(
- cache_size_limit: OptionalType[int] = None, *, force=False
+ cache_size_limit: typing.Optional[int] = None, *, force=False
) -> None:
"""
Enables "bounded recursion" parsing, which allows for both direct and indirect
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
patt.parse_string('ablaj /* comment */ lskjd')
# -> ['ablaj']
# turn on debugging for wd
wd.set_debug()
- OneOrMore(term).parse_string("abc 123 xyz 890")
+ term[1, ...].parse_string("abc 123 xyz 890")
prints::
self,
tests: Union[str, List[str]],
parse_all: bool = True,
- comment: OptionalType[Union["ParserElement", str]] = "#",
+ comment: typing.Optional[Union["ParserElement", str]] = "#",
full_dump: bool = True,
print_results: bool = True,
failure_tests: bool = False,
post_parse: Callable[[str, ParseResults], str] = None,
- file: OptionalType[TextIO] = None,
+ file: typing.Optional[TextIO] = None,
with_line_numbers: bool = False,
*,
parseAll: bool = True,
def __init__(
self,
match_string: str = "",
- ident_chars: OptionalType[str] = None,
+ ident_chars: typing.Optional[str] = None,
caseless: bool = False,
*,
matchString: str = "",
- identChars: OptionalType[str] = None,
+ identChars: typing.Optional[str] = None,
):
super().__init__()
identChars = identChars or ident_chars
Example::
- OneOrMore(CaselessLiteral("CMD")).parse_string("cmd CMD Cmd10")
+ CaselessLiteral("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD', 'CMD']
(Contrast with example for :class:`CaselessKeyword`.)
Example::
- OneOrMore(CaselessKeyword("CMD")).parse_string("cmd CMD Cmd10")
+ CaselessKeyword("CMD")[1, ...].parse_string("cmd CMD Cmd10")
# -> ['CMD', 'CMD']
(Contrast with example for :class:`CaselessLiteral`.)
def __init__(
self,
match_string: str = "",
- ident_chars: OptionalType[str] = None,
+ ident_chars: typing.Optional[str] = None,
*,
matchString: str = "",
- identChars: OptionalType[str] = None,
+ identChars: typing.Optional[str] = None,
):
identChars = identChars or ident_chars
match_string = matchString or match_string
def __init__(
self,
init_chars: str = "",
- body_chars: OptionalType[str] = None,
+ body_chars: typing.Optional[str] = None,
min: int = 1,
max: int = 0,
exact: int = 0,
as_keyword: bool = False,
- exclude_chars: OptionalType[str] = None,
+ exclude_chars: typing.Optional[str] = None,
*,
- initChars: OptionalType[str] = None,
- bodyChars: OptionalType[str] = None,
+ initChars: typing.Optional[str] = None,
+ bodyChars: typing.Optional[str] = None,
asKeyword: bool = False,
- excludeChars: OptionalType[str] = None,
+ excludeChars: typing.Optional[str] = None,
):
initChars = initChars or init_chars
bodyChars = bodyChars or body_chars
self,
charset: str,
as_keyword: bool = False,
- exclude_chars: OptionalType[str] = None,
+ exclude_chars: typing.Optional[str] = None,
*,
asKeyword: bool = False,
- excludeChars: OptionalType[str] = None,
+ excludeChars: typing.Optional[str] = None,
):
asKeyword = asKeyword or as_keyword
excludeChars = excludeChars or exclude_chars
def __init__(
self,
quote_char: str = "",
- esc_char: OptionalType[str] = None,
- esc_quote: OptionalType[str] = None,
+ esc_char: typing.Optional[str] = None,
+ esc_quote: typing.Optional[str] = None,
multiline: bool = False,
unquote_results: bool = True,
- end_quote_char: OptionalType[str] = None,
+ end_quote_char: typing.Optional[str] = None,
convert_whitespace_escapes: bool = True,
*,
quoteChar: str = "",
- escChar: OptionalType[str] = None,
- escQuote: OptionalType[str] = None,
+ escChar: typing.Optional[str] = None,
+ escQuote: typing.Optional[str] = None,
unquoteResults: bool = True,
- endQuoteChar: OptionalType[str] = None,
+ endQuoteChar: typing.Optional[str] = None,
convertWhitespaceEscapes: bool = True,
):
super().__init__()
post-processing parsed tokens.
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(savelist)
self.exprs: List[ParserElement]
if isinstance(exprs, _generatorType):
Example::
integer = Word(nums)
- name_expr = OneOrMore(Word(alphas))
+ name_expr = Word(alphas)[1, ...]
expr = And([integer("id"), name_expr("name"), integer("age")])
# more easily written as:
def _generateDefaultName(self):
return "-"
- def __init__(self, exprs_arg: IterableType[ParserElement], savelist: bool = True):
+ def __init__(
+ self, exprs_arg: typing.Iterable[ParserElement], savelist: bool = True
+ ):
exprs: List[ParserElement] = list(exprs_arg)
if exprs and Ellipsis in exprs:
tmp = []
[['123'], ['3.1416'], ['789']]
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
print(number.search_string("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']]
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = False):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = False):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
- size: 20
"""
- def __init__(self, exprs: IterableType[ParserElement], savelist: bool = True):
+ def __init__(self, exprs: typing.Iterable[ParserElement], savelist: bool = True):
super().__init__(exprs, savelist)
if self.exprs:
self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
label = data_word + FollowedBy(':')
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
- OneOrMore(attr_expr).parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
+ attr_expr[1, ...].parse_string("shape: SQUARE color: BLACK posn: upper left").pprint()
prints::
"""
def __init__(
- self, expr: Union[ParserElement, str], retreat: OptionalType[int] = None
+ self, expr: Union[ParserElement, str], retreat: typing.Optional[int] = None
):
super().__init__(expr)
self.expr = self.expr().leave_whitespace()
# very crude boolean expression - to support parenthesis groups and
# operation hierarchy, use infix_notation
- boolean_expr = boolean_term + ZeroOrMore((AND | OR) + boolean_term)
+ boolean_expr = boolean_term + ((AND | OR) + boolean_term)[...]
# integers that are followed by "." are actually floats
integer = Word(nums) + ~Char(".")
def __init__(
self,
expr: ParserElement,
- stop_on: OptionalType[Union[ParserElement, str]] = None,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
*,
- stopOn: OptionalType[Union[ParserElement, str]] = None,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
):
super().__init__(expr)
stopOn = stopOn or stop_on
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).set_parse_action(' '.join))
text = "shape: SQUARE posn: upper left color: BLACK"
- OneOrMore(attr_expr).parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+ attr_expr[1, ...].parse_string(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
# use stop_on attribute for OneOrMore to avoid reading label string as part of the data
attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
def __init__(
self,
expr: ParserElement,
- stop_on: OptionalType[Union[ParserElement, str]] = None,
+ stop_on: typing.Optional[Union[ParserElement, str]] = None,
*,
- stopOn: OptionalType[Union[ParserElement, str]] = None,
+ stopOn: typing.Optional[Union[ParserElement, str]] = None,
):
super().__init__(expr, stopOn=stopOn or stop_on)
self.mayReturnEmpty = True
other: Union[ParserElement, str],
include: bool = False,
ignore: bool = None,
- fail_on: OptionalType[Union[ParserElement, str]] = None,
+ fail_on: typing.Optional[Union[ParserElement, str]] = None,
*,
failOn: Union[ParserElement, str] = None,
):
parser created using ``Forward``.
"""
- def __init__(self, other: OptionalType[Union[ParserElement, str]] = None):
+ def __init__(self, other: typing.Optional[Union[ParserElement, str]] = None):
self.caller_frame = traceback.extract_stack(limit=2)[0]
super().__init__(other, savelist=False)
self.lshift_line = None
join_string: str = "",
adjacent: bool = True,
*,
- joinString: OptionalType[str] = None,
+ joinString: typing.Optional[str] = None,
):
super().__init__(expr)
joinString = joinString if joinString is not None else join_string
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
# print attributes as plain groups
- print(OneOrMore(attr_expr).parse_string(text).dump())
+ print(attr_expr[1, ...].parse_string(text).dump())
- # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
- result = Dict(OneOrMore(Group(attr_expr))).parse_string(text)
+ # instead of OneOrMore(expr), parse using Dict(Group(expr)[1, ...]) - Dict will auto-assign names
+ result = Dict(Group(attr_expr)[1, ...]).parse_string(text)
print(result.dump())
# access named fields as dict entries, or output as dict
source = "a, b, c,d"
wd = Word(alphas)
- wd_list1 = wd + ZeroOrMore(',' + wd)
+ wd_list1 = wd + (',' + wd)[...]
print(wd_list1.parse_string(source))
# often, delimiters that are useful during parsing are just in the
# way afterward - use Suppress to keep them out of the parsed output
- wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+ wd_list2 = wd + (Suppress(',') + wd)[...]
print(wd_list2.parse_string(source))
# Skipped text (using '...') can be suppressed as well
def remove_duplicate_chars(tokens):
return ''.join(sorted(set(''.join(tokens))))
- wds = OneOrMore(wd).set_parse_action(remove_duplicate_chars)
+ wds = wd[1, ...].set_parse_action(remove_duplicate_chars)
print(wds.parse_string("slkdjs sld sldd sdlf sdljf"))
prints::
Example (compare the last to example in :class:`ParserElement.transform_string`::
- hex_ints = OneOrMore(Word(hexnums)).set_parse_action(token_map(int, 16))
+ hex_ints = Word(hexnums)[1, ...].set_parse_action(token_map(int, 16))
hex_ints.run_tests('''
00 11 22 aa FF 0a 0d 1a
''')
upperword = Word(alphas).set_parse_action(token_map(str.upper))
- OneOrMore(upperword).run_tests('''
+ upperword[1, ...].run_tests('''
my kingdom for a horse
''')
wd = Word(alphas).set_parse_action(token_map(str.title))
- OneOrMore(wd).set_parse_action(' '.join).run_tests('''
+ wd[1, ...].set_parse_action(' '.join).run_tests('''
now is the winter of our discontent made glorious summer by this sun of york
''')
# build list of built-in expressions, for future reference if a global default value
# gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
# backward compatibility names
tokenMap = token_map
import railroad
import pyparsing
-from pkg_resources import resource_filename
+import typing
from typing import (
List,
- Optional,
NamedTuple,
Generic,
TypeVar,
import inspect
-with open(resource_filename(__name__, "template.jinja2"), encoding="utf-8") as fp:
- template = Template(fp.read())
+jinja2_template_source = """\
+<!DOCTYPE html>
+<html>
+<head>
+ {% if not head %}
+ <style type="text/css">
+ .railroad-heading {
+ font-family: monospace;
+ }
+ </style>
+ {% else %}
+ {{ head | safe }}
+ {% endif %}
+</head>
+<body>
+{{ body | safe }}
+{% for diagram in diagrams %}
+ <div class="railroad-group">
+ <h1 class="railroad-heading">{{ diagram.title }}</h1>
+ <div class="railroad-description">{{ diagram.text }}</div>
+ <div class="railroad-svg">
+ {{ diagram.svg }}
+ </div>
+ </div>
+{% endfor %}
+</body>
+</html>
+"""
+
+template = Template(jinja2_template_source)
# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet
NamedDiagram = NamedTuple(
"NamedDiagram",
- [("name", str), ("diagram", Optional[railroad.DiagramItem]), ("index", int)],
+ [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)],
)
"""
A simple structure for associating a name with a railroad diagram
"""
data = []
for diagram in diagrams:
+ if diagram.diagram is None:
+ continue
io = StringIO()
diagram.diagram.writeSvg(io.write)
title = diagram.name
def to_railroad(
element: pyparsing.ParserElement,
- diagram_kwargs: Optional[dict] = None,
+ diagram_kwargs: typing.Optional[dict] = None,
vertical: int = 3,
show_results_names: bool = False,
show_groups: bool = False,
parent: EditablePartial,
number: int,
name: str = None,
- parent_index: Optional[int] = None,
+ parent_index: typing.Optional[int] = None,
):
#: The pyparsing element that this represents
self.element: pyparsing.ParserElement = element
#: The name of the element
- self.name: str = name
+ self.name: typing.Optional[str] = name
#: The output Railroad element in an unconverted state
self.converted: EditablePartial = converted
#: The parent Railroad element, which we store so that we can extract this if it's duplicated
#: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram
self.number: int = number
#: The index of this inside its parent
- self.parent_index: Optional[int] = parent_index
+ self.parent_index: typing.Optional[int] = parent_index
#: If true, we should extract this out into a subdiagram
self.extract: bool = False
#: If true, all of this element's children have been filled out
Stores some state that persists between recursions into the element tree
"""
- def __init__(self, diagram_kwargs: Optional[dict] = None):
+ def __init__(self, diagram_kwargs: typing.Optional[dict] = None):
#: A dictionary mapping ParserElements to state relating to them
self._element_diagram_states: Dict[int, ElementState] = {}
#: A dictionary mapping ParserElement IDs to subdiagrams generated from them
def _inner(
element: pyparsing.ParserElement,
- parent: Optional[EditablePartial],
+ parent: typing.Optional[EditablePartial],
lookup: ConverterState = None,
vertical: int = None,
index: int = 0,
name_hint: str = None,
show_results_names: bool = False,
show_groups: bool = False,
- ) -> Optional[EditablePartial]:
+ ) -> typing.Optional[EditablePartial]:
ret = fn(
element,
@_apply_diagram_item_enhancements
def _to_diagram_element(
element: pyparsing.ParserElement,
- parent: Optional[EditablePartial],
+ parent: typing.Optional[EditablePartial],
lookup: ConverterState = None,
vertical: int = None,
index: int = 0,
name_hint: str = None,
show_results_names: bool = False,
show_groups: bool = False,
-) -> Optional[EditablePartial]:
+) -> typing.Optional[EditablePartial]:
"""
Recursively converts a PyParsing Element to a railroad Element
:param lookup: The shared converter state that keeps track of useful things
else:
ret = EditablePartial.from_call(railroad.Group, label="", item="")
elif isinstance(element, pyparsing.TokenConverter):
- ret = EditablePartial.from_call(AnnotatedItem, label=type(element).__name__.lower(), item="")
+ ret = EditablePartial.from_call(
+ AnnotatedItem, label=type(element).__name__.lower(), item=""
+ )
elif isinstance(element, pyparsing.Opt):
ret = EditablePartial.from_call(railroad.Optional, item="")
elif isinstance(element, pyparsing.OneOrMore):
+++ /dev/null
-<!DOCTYPE html>
-<html>
-<head>
- {% if not head %}
- <style type="text/css">
- .railroad-heading {
- font-family: monospace;
- }
- </style>
- {% else %}
- {{ hear | safe }}
- {% endif %}
-</head>
-<body>
-{{ body | safe }}
-{% for diagram in diagrams %}
- <div class="railroad-group">
- <h1 class="railroad-heading">{{ diagram.title }}</h1>
- <div class="railroad-description">{{ diagram.text }}</div>
- <div class="railroad-svg">
- {{ diagram.svg }}
- </div>
- </div>
-{% endfor %}
-</body>
-</html>
import re
import sys
-from typing import Optional
+import typing
from .util import col, line, lineno, _collapse_string_to_ranges
from .unicode import pyparsing_unicode as ppu
self,
pstr: str,
loc: int = 0,
- msg: Optional[str] = None,
+ msg: typing.Optional[str] = None,
elem=None,
):
self.loc = loc
# helpers.py
import html.entities
import re
+import typing
from . import __diag__
from .core import *
expr: Union[str, ParserElement],
delim: Union[str, ParserElement] = ",",
combine: bool = False,
- min: OptionalType[int] = None,
- max: OptionalType[int] = None,
+ min: typing.Optional[int] = None,
+ max: typing.Optional[int] = None,
*,
allow_trailing_delim: bool = False,
) -> ParserElement:
def counted_array(
expr: ParserElement,
- int_expr: OptionalType[ParserElement] = None,
+ int_expr: typing.Optional[ParserElement] = None,
*,
- intExpr: OptionalType[ParserElement] = None,
+ intExpr: typing.Optional[ParserElement] = None,
) -> ParserElement:
"""Helper to define a counted list of expressions.
def one_of(
- strs: Union[IterableType[str], str],
+ strs: Union[typing.Iterable[str], str],
caseless: bool = False,
use_regex: bool = True,
as_keyword: bool = False,
text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join))
- print(OneOrMore(attr_expr).parse_string(text).dump())
+ print(attr_expr[1, ...].parse_string(text).dump())
attr_label = label
attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)
def nested_expr(
opener: Union[str, ParserElement] = "(",
closer: Union[str, ParserElement] = ")",
- content: OptionalType[ParserElement] = None,
+ content: typing.Optional[ParserElement] = None,
ignore_expr: ParserElement = quoted_string(),
*,
ignoreExpr: ParserElement = quoted_string(),
return _makeTags(tag_str, True)
+any_open_tag: ParserElement
+any_close_tag: ParserElement
any_open_tag, any_close_tag = make_html_tags(
Word(alphas, alphanums + "_:").set_name("any tag")
)
InfixNotationOperatorArgType,
int,
OpAssoc,
- OptionalType[ParseAction],
+ typing.Optional[ParseAction],
],
Tuple[
InfixNotationOperatorArgType,
if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT):
raise ValueError("operator must indicate right or left associativity")
- thisExpr = Forward().set_name(term_name)
+ thisExpr: Forward = Forward().set_name(term_name)
if rightLeftAssoc is OpAssoc.LEFT:
if arity == 1:
matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...])
assignment = Group(identifier + "=" + rvalue)
stmt << (funcDef | assignment | identifier)
- module_body = OneOrMore(stmt)
+ module_body = stmt[1, ...]
parseTree = module_body.parseString(data)
parseTree.pprint()
# build list of built-in expressions, for future reference if a global default value
# gets updated
-_builtin_exprs = [v for v in vars().values() if isinstance(v, ParserElement)]
+_builtin_exprs: List[ParserElement] = [
+ v for v in vars().values() if isinstance(v, ParserElement)
+]
# pre-PEP8 compatible names
print(numlist.parse_string("0 123 321")) # -> ['123', '321']
label = Word(alphas)
- patt = label("LABEL") + OneOrMore(Word(nums))
+ patt = label("LABEL") + Word(nums)[1, ...]
print(patt.parse_string("AAB 123 321").dump())
# Use pop() in a parse action to remove named result (note that corresponding value is not
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
# use a parse action to append the reverse of the matched strings, to make a palindrome
def make_palindrome(tokens):
Example::
- patt = OneOrMore(Word(alphas))
+ patt = Word(alphas)[1, ...]
result = patt.parse_string("sldkj lsdkj sldkj")
# even though the result prints in string-like form, it is actually a pyparsing ParseResults
print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj']
user_data = (Group(house_number_expr)("house_number")
| Group(ssn_expr)("ssn")
| Group(integer)("age"))
- user_info = OneOrMore(user_data)
+ user_info = user_data[1, ...]
result = user_info.parse_string("22 111-22-3333 #221B")
for item in result:
# testing.py
from contextlib import contextmanager
-from typing import Optional
+import typing
from .core import (
ParserElement,
@staticmethod
def with_line_numbers(
s: str,
- start_line: Optional[int] = None,
- end_line: Optional[int] = None,
+ start_line: typing.Optional[int] = None,
+ end_line: typing.Optional[int] = None,
expand_tabs: bool = True,
eol_mark: str = "|",
- mark_spaces: Optional[str] = None,
- mark_control: Optional[str] = None,
+ mark_spaces: typing.Optional[str] = None,
+ mark_control: typing.Optional[str] = None,
) -> str:
"""
Helpful method for debugging a parser - prints a string with line and column numbers.
A namespace class for defining common language unicode_sets.
"""
- _ranges: UnicodeRangeList = [(32, sys.maxunicode)]
+ # fmt: off
+
+ # define ranges in language character sets
+ _ranges: UnicodeRangeList = [
+ (0x0020, sys.maxunicode),
+ ]
+
+ class BasicMultilingualPlane(unicode_set):
+ "Unicode set for the Basic Multilingual Plane"
+ _ranges: UnicodeRangeList = [
+ (0x0020, 0xFFFF),
+ ]
class Latin1(unicode_set):
"Unicode set for Latin-1 Unicode Character Range"
class CJK(Chinese, Japanese, Hangul):
"Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range"
- pass
class Thai(unicode_set):
"Unicode set for Thai Unicode Character Range"
- _ranges: UnicodeRangeList = [(0x0E01, 0x0E3A), (0x0E3F, 0x0E5B)]
+ _ranges: UnicodeRangeList = [
+ (0x0E01, 0x0E3A),
+ (0x0E3F, 0x0E5B)
+ ]
class Arabic(unicode_set):
"Unicode set for Arabic Unicode Character Range"
class Devanagari(unicode_set):
"Unicode set for Devanagari Unicode Character Range"
- _ranges: UnicodeRangeList = [(0x0900, 0x097F), (0xA8E0, 0xA8FF)]
+ _ranges: UnicodeRangeList = [
+ (0x0900, 0x097F),
+ (0xA8E0, 0xA8FF)
+ ]
+
+ # fmt: on
pyparsing_unicode.Japanese._ranges = (
+ pyparsing_unicode.Japanese.Katakana._ranges
)
-# define ranges in language character sets
+pyparsing_unicode.BMP = pyparsing_unicode.BasicMultilingualPlane
+
+# add language identifiers using language Unicode
pyparsing_unicode.العربية = pyparsing_unicode.Arabic
pyparsing_unicode.中文 = pyparsing_unicode.Chinese
pyparsing_unicode.кириллица = pyparsing_unicode.Cyrillic
packaging==21.3
-pyparsing==3.0.8
+pyparsing==3.0.9
ordered-set==3.1.1
more_itertools==8.8.0
jaraco.text==3.7.0
try:
return validator.validate(config)
except validator.ValidationError as ex:
- _logger.error(f"configuration error: {ex.summary}") # type: ignore
- _logger.debug(ex.details) # type: ignore
- error = ValueError(f"invalid pyproject.toml config: {ex.name}") # type: ignore
- raise error from None
+ summary = f"configuration error: {ex.summary}"
+ if ex.name.strip("`") != "project":
+ # Probably it is just a field missing/misnamed, not worthy the verbosity...
+ _logger.debug(summary)
+ _logger.debug(ex.details)
+
+ error = f"invalid pyproject.toml config: {ex.name}."
+ raise ValueError(f"{error}\n{summary}") from None
def apply_configuration(
"""
import os
-import warnings
+import contextlib
import functools
+import warnings
from collections import defaultdict
from functools import partial
from functools import wraps
Optional, Tuple, TypeVar, Union)
from distutils.errors import DistutilsOptionError, DistutilsFileError
+from setuptools.extern.packaging.requirements import Requirement, InvalidRequirement
from setuptools.extern.packaging.version import Version, InvalidVersion
from setuptools.extern.packaging.specifiers import SpecifierSet
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
return meta, options
+def _warn_accidental_env_marker_misconfig(label: str, orig_value: str, parsed: list):
+ """Because users sometimes misinterpret this configuration:
+
+ [options.extras_require]
+ foo = bar;python_version<"4"
+
+ It looks like one requirement with an environment marker
+ but because there is no newline, it's parsed as two requirements
+ with a semicolon as separator.
+
+ Therefore, if:
+ * input string does not contain a newline AND
+ * parsed result contains two requirements AND
+ * parsing of the two parts from the result ("<first>;<second>")
+ leads in a valid Requirement with a valid marker
+ a UserWarning is shown to inform the user about the possible problem.
+ """
+ if "\n" in orig_value or len(parsed) != 2:
+ return
+
+ with contextlib.suppress(InvalidRequirement):
+ original_requirements_str = ";".join(parsed)
+ req = Requirement(original_requirements_str)
+ if req.marker is not None:
+ msg = (
+ f"One of the parsed requirements in `{label}` "
+ f"looks like a valid environment marker: '{parsed[1]}'\n"
+ "Make sure that the config is correct and check "
+ "https://setuptools.pypa.io/en/latest/userguide/declarative_config.html#opt-2" # noqa: E501
+ )
+ warnings.warn(msg, UserWarning)
+
+
class ConfigHandler(Generic[Target]):
"""Handles metadata supplied in configuration files."""
return parse
@classmethod
- def _parse_section_to_dict(cls, section_options, values_parser=None):
+ def _parse_section_to_dict_with_key(cls, section_options, values_parser):
"""Parses section options into a dictionary.
- Optionally applies a given parser to values.
+ Applies a given parser to each option in a section.
:param dict section_options:
- :param callable values_parser:
+ :param callable values_parser: function with 2 args corresponding to key, value
:rtype: dict
"""
value = {}
- values_parser = values_parser or (lambda val: val)
for key, (_, val) in section_options.items():
- value[key] = values_parser(val)
+ value[key] = values_parser(key, val)
return value
+ @classmethod
+ def _parse_section_to_dict(cls, section_options, values_parser=None):
+ """Parses section options into a dictionary.
+
+ Optionally applies a given parser to each value.
+
+ :param dict section_options:
+ :param callable values_parser: function with 1 arg corresponding to option value
+ :rtype: dict
+ """
+ parser = (lambda _, v: values_parser(v)) if values_parser else (lambda _, v: v)
+ return cls._parse_section_to_dict_with_key(section_options, parser)
+
def parse_section(self, section_options):
"""Parses configuration file section.
:param dict section_options:
"""
for (name, (_, value)) in section_options.items():
- try:
+ with contextlib.suppress(KeyError):
+ # Keep silent for a new option may appear anytime.
self[name] = value
- except KeyError:
- pass # Keep silent for a new option may appear anytime.
-
def parse(self):
"""Parses configuration file items from one
or more related sections.
def _parse_file_in_root(self, value):
return self._parse_file(value, root_dir=self.root_dir)
- def _parse_requirements_list(self, value):
+ def _parse_requirements_list(self, label: str, value: str):
# Parse a requirements list, either by reading in a `file:`, or a list.
parsed = self._parse_list_semicolon(self._parse_file_in_root(value))
+ _warn_accidental_env_marker_misconfig(label, value, parsed)
# Filter it to only include lines that are not comments. `parse_list`
# will have stripped each line and filtered out empties.
return [line for line in parsed if not line.startswith("#")]
"consider using implicit namespaces instead (PEP 420).",
SetuptoolsDeprecationWarning,
),
- 'install_requires': self._parse_requirements_list,
+ 'install_requires': partial(
+ self._parse_requirements_list, "install_requires"
+ ),
'setup_requires': self._parse_list_semicolon,
'tests_require': self._parse_list_semicolon,
'packages': self._parse_packages,
:param dict section_options:
"""
- parsed = self._parse_section_to_dict(
+ parsed = self._parse_section_to_dict_with_key(
section_options,
- self._parse_requirements_list,
+ lambda k, v: self._parse_requirements_list(f"extras_require[{k}]", v)
)
+
self['extras_require'] = parsed
def parse_section_data_files(self, section_options):
:keyword list[str] runtime_library_dirs:
list of directories to search for C/C++ libraries at run time
- (for shared extensions, this is when the extension is loaded)
+ (for shared extensions, this is when the extension is loaded).
+ Setting this will cause an exception during build on Windows
+ platforms.
:keyword list[str] extra_objects:
list of extra files to link with (eg. object files not implied
:keyword bool optional:
specifies that a build failure in the extension should not abort the
build process, but simply not install the failing extension.
+
+ :raises setuptools.errors.PlatformError: if 'runtime_library_dirs' is
+ specified on Windows. (since v63)
"""
def __init__(self, name, sources, *args, **kw):
-import logging
import re
from configparser import ConfigParser
from inspect import cleandoc
@pytest.mark.parametrize(
- "example, error_msg, value_shown_in_debug",
+ "example, error_msg",
[
(
"""
version = "1.2"
requires = ['pywin32; platform_system=="Windows"' ]
""",
- "configuration error: `project` must not contain {'requires'} properties",
- '"requires": ["pywin32; platform_system==\\"Windows\\""]',
+ "configuration error: .project. must not contain ..requires.. properties",
),
],
)
-def test_invalid_example(tmp_path, caplog, example, error_msg, value_shown_in_debug):
- caplog.set_level(logging.DEBUG)
+def test_invalid_example(tmp_path, example, error_msg):
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(cleandoc(example))
- caplog.clear()
- with pytest.raises(ValueError, match="invalid pyproject.toml"):
+ pattern = re.compile(f"invalid pyproject.toml.*{error_msg}.*", re.M | re.S)
+ with pytest.raises(ValueError, match=pattern):
read_configuration(pyproject)
- # Make sure the logs give guidance to the user
- error_log = caplog.record_tuples[0]
- assert error_log[1] == logging.ERROR
- assert error_msg in error_log[2]
-
- debug_log = caplog.record_tuples[1]
- assert debug_log[1] == logging.DEBUG
- debug_msg = "".join(line.strip() for line in debug_log[2].splitlines())
- assert value_shown_in_debug in debug_msg
-
@pytest.mark.parametrize("config", ("", "[tool.something]\nvalue = 42"))
def test_empty(tmp_path, config):
}
assert dist.metadata.provides_extras == set(['pdf', 'rest'])
+ @pytest.mark.parametrize(
+ "config",
+ [
+ "[options.extras_require]\nfoo = bar;python_version<'3'",
+ "[options.extras_require]\nfoo = bar;os_name=='linux'",
+ "[options.extras_require]\nfoo = bar;python_version<'3'\n",
+ "[options.extras_require]\nfoo = bar;os_name=='linux'\n",
+ "[options]\ninstall_requires = bar;python_version<'3'",
+ "[options]\ninstall_requires = bar;os_name=='linux'",
+ "[options]\ninstall_requires = bar;python_version<'3'\n",
+ "[options]\ninstall_requires = bar;os_name=='linux'\n",
+ ],
+ )
+ def test_warn_accidental_env_marker_misconfig(self, config, tmpdir):
+ fake_env(tmpdir, config)
+ match = (
+ r"One of the parsed requirements in `(install_requires|extras_require.+)` "
+ "looks like a valid environment marker.*"
+ )
+ with pytest.warns(UserWarning, match=match):
+ with get_dist(tmpdir) as _:
+ pass
+
+ @pytest.mark.parametrize(
+ "config",
+ [
+ "[options.extras_require]\nfoo =\n bar;python_version<'3'",
+ "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy",
+ "[options.extras_require]\nfoo =\n bar;python_version<'3'\n",
+ "[options.extras_require]\nfoo = bar;baz\nboo = xxx;yyy\n",
+ "[options.extras_require]\nfoo =\n bar\n python_version<'3'\n",
+ "[options]\ninstall_requires =\n bar;python_version<'3'",
+ "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy",
+ "[options]\ninstall_requires =\n bar;python_version<'3'\n",
+ "[options]\ninstall_requires = bar;baz\nboo = xxx;yyy\n",
+ "[options]\ninstall_requires =\n bar\n python_version<'3'\n",
+ ],
+ )
+ def test_nowarn_accidental_env_marker_misconfig(self, config, tmpdir, recwarn):
+ fake_env(tmpdir, config)
+ with get_dist(tmpdir) as _:
+ pass
+ # The examples are valid, no warnings shown
+ assert not any(w.category == UserWarning for w in recwarn)
+
def test_dash_preserved_extras_require(self, tmpdir):
fake_env(tmpdir, '[options.extras_require]\n' 'foo-a = foo\n' 'foo_b = test\n')