From d50602441977b26811a1c69532c54d2786f76142 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 24 Apr 2022 15:17:31 -0400 Subject: [PATCH 01/90] updated to python 3, refactored code, added typehints --- .gitignore | 6 +- verbalexpressions/__init__.py | 2 +- verbalexpressions/verbal_expressions.py | 346 ++++++++++++++++++------ verbex.py | 41 +++ 4 files changed, 311 insertions(+), 84 deletions(-) create mode 100644 verbex.py diff --git a/.gitignore b/.gitignore index 8904a1f..ba0430d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -venv -dist -build -*.py[co] -*.egg-info +__pycache__/ \ No newline at end of file diff --git a/verbalexpressions/__init__.py b/verbalexpressions/__init__.py index bc28e0a..233a1e0 100644 --- a/verbalexpressions/__init__.py +++ b/verbalexpressions/__init__.py @@ -1 +1 @@ -from verbalexpressions.verbal_expressions import VerEx, re_escape +from .verbal_expressions import VerbEx, re_escape diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index c1a22c8..eb65d10 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -1,133 +1,323 @@ +from __future__ import annotations + import re +from enum import Enum +from functools import wraps +from typing import Protocol + +try: + from beartype import beartype # type: ignore + from beartype.typing import ( # type: ignore + Any, + Callable, + Dict, + Iterator, + List, + Optional, + ParamSpec, + Tuple, + TypeVar, + Union, + cast, + runtime_checkable, + ) +except ModuleNotFoundError: + from typing import ( + Any, + Callable, + Dict, + Iterator, + List, + Optional, + ParamSpec, + Tuple, + TypeVar, + Union, + cast, + runtime_checkable, + ) + + __P = ParamSpec("__P") + __R = TypeVar("__R") + + def noop_dec(func: Callable[__P, __R]) -> Callable[__P, __R]: + return func + + beartype = noop_dec # type: ignore + + +P = ParamSpec("P") +R = TypeVar("R") + + +# work around for bug https://github.com/python/mypy/issues/12660 +@runtime_checkable +class HasIter(Protocol): + def __iter__(self) -> Iterator[Any]: + ... + + +# work around for bug https://github.com/python/mypy/issues/12660 +@runtime_checkable +class HasItems(Protocol): + def items(self) -> Tuple[str, Any]: + ... + + +class EscapedText(str): + def __new__(cls, value: str): + return str.__new__(cls, re.escape(value)) + + +def re_escape(func: Callable[P, R]) -> Callable[P, R]: + @wraps(func) + def inner(*args: P.args, **kwargs: P.kwargs) -> R: + escaped_args: List[Any] = [] + escaped_kwargs: Dict[str, Any] = {} + for arg in cast(HasIter, args): + if isinstance(arg, str): + escaped_args.append(EscapedText(re.escape(arg))) + else: + escaped_args.append(arg) + arg_k: str + arg_v: Any + for arg_k, arg_v in cast(HasItems, kwargs).items(): + if isinstance(arg_v, str): + escaped_kwargs[arg_k] = EscapedText(re.escape(str(arg_v))) + else: + escaped_kwargs[arg_k] = arg_v + return func(*escaped_args, **escaped_kwargs) # type: ignore + + return inner + + +class CharClass(Enum): + DIGIT = "\\d" + LETTER = "\\w" + UPPERCASE_LETTER = "\\u" + LOWERCASE_LETTER = "\\l" + WHITESPACE = "\\s" + TAB = "\\t" + + def __str__(self): + return self.value -def re_escape(fn): - def arg_escaped(this, *args): - t = [isinstance(a, VerEx) and a.s or re.escape(str(a)) for a in args] - return fn(this, *t) +class SpecialChar(Enum): + # does not work / should not be used in [ ] + LINEBREAK = "(\\n|(\\r\\n))" + START_OF_LINE = "^" + END_OF_LINE = "$" - return arg_escaped + def __str__(self): + return self.value -class VerEx(object): - """ - --- VerbalExpressions class --- - the following methods behave different from the original js lib! +CharClassOrChars = Union[str, CharClass] +EscapedCharClassOrSpecial = Union[str, CharClass, SpecialChar] +VerbexEscapedCharClassOrSpecial = Union["Verbex", EscapedCharClassOrSpecial] - - end_of_line - - start_of_line - - or - when you say you want `$`, `^` and `|`, we just insert it right there. - No other tricks. - And any string you inserted will be automatically grouped - except `tab` and `add`. +class Verbex: + """ + VerbalExpressions class. + the following methods do not try to match the original js lib! """ - def __init__(self): - self.s = [] - self.modifiers = {"I": 0, "M": 0, "A": 0} + @re_escape + @beartype + def __init__(self, modifiers: re.RegexFlag = re.RegexFlag(0)): + # self._parts: List[str] = [text] + self._parts: List[str] = [] + self._modifiers = modifiers - def __getattr__(self, attr): - """ any other function will be sent to the regex object """ - regex = self.regex() - return getattr(regex, attr) + @property + def modifiers(self) -> re.RegexFlag: + return self._modifiers def __str__(self): - return "".join(self.s) + """Return regex string representation.""" + return "".join(self._parts) + + @beartype + def __add(self, value: Union[str, List[str]]): + """ + Append a transformed value to internal expression to be compiled. - def add(self, value): + As possible, this method should be "private". + """ if isinstance(value, list): - self.s.extend(value) + self._parts.extend(value) else: - self.s.append(value) + self._parts.append(value) return self def regex(self): - """ get a regular expression object. """ + """get a regular expression object.""" return re.compile( str(self), - self.modifiers["I"] | self.modifiers["M"] | self.modifiers["A"], + self._modifiers, ) - compile = regex - - def source(self): - """ return the raw string """ - return str(self) + # allow VerbexEscapedCharClassOrSpecial - raw = value = source + @re_escape + @beartype + def _capture_group_with_name( + self, + name: str, + text: VerbexEscapedCharClassOrSpecial, + ) -> Verbex: + return self.__add(f"(?<{name}>{str(text)})") - # --------------------------------------------- + @re_escape + @beartype + def _capture_group_without_name( + self, + text: VerbexEscapedCharClassOrSpecial, + ) -> Verbex: + return self.__add(f"({str(text)})") - def anything(self): - return self.add("(.*)") + @re_escape + @beartype + def capture_group( + self, + /, + name_or_text: Union[ + Optional[str], VerbexEscapedCharClassOrSpecial + ] = None, + text: Optional[VerbexEscapedCharClassOrSpecial] = None, + ) -> Verbex: + if name_or_text is not None: + if text is None: + _text = name_or_text + return self._capture_group_without_name(_text) + if isinstance(name_or_text, str): + return self._capture_group_with_name(name_or_text, text) + raise ValueError("text must be specified with optional name") @re_escape - def anything_but(self, value): - return self.add("([^%s]*)" % value) + @beartype + def OR(self, text: VerbexEscapedCharClassOrSpecial): # noqa N802 + """`or` is a python keyword so we use `OR` instead.""" + self.__add("|") + self.find(text) - def end_of_line(self): - return self.add("$") + @re_escape + @beartype + def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + return self.__add(f"(?:{str(text)})*") @re_escape - def maybe(self, value): - return self.add("(%s)?" % value) + @beartype + def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + return self.__add(f"(?:{str(text)})+") - def start_of_line(self): - return self.add("^") + @re_escape + @beartype + def n_times(self, text: VerbexEscapedCharClassOrSpecial, n: int) -> Verbex: + return self.__add(f"(?:{str(text)}){{{n}}}") @re_escape - def find(self, value): - return self.add("(%s)" % value) + @beartype + def n_times_or_more( + self, text: VerbexEscapedCharClassOrSpecial, n: int + ) -> Verbex: + return self.__add(f"(?:{str(text)}){{{n},}}") - then = find + @re_escape + @beartype + def n_to_m_times( + self, text: VerbexEscapedCharClassOrSpecial, n: int, m: int + ) -> Verbex: + return self.__add(f"(?:{str(text)}){{{n},{m}}}") - # special characters and groups + @re_escape + def anything_but(self, text: EscapedCharClassOrSpecial): + return self.__add(f"[^{text}]*") @re_escape - def any(self, value): - return self.add("([%s])" % value) + @beartype + def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + # if isinstance(text, Verbex): + # return self.__add(f"(?:{str(text)})?") + return self.__add(f"(?:{str(text)})?") - any_of = any + @re_escape + @beartype + def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + return self.__add(str(text)) - def line_break(self): - return self.add(r"(\n|(\r\n))") + # only allow CharclassOrChars - br = line_break + @re_escape + @beartype + def any_of(self, text: CharClassOrChars) -> Verbex: + return self.__add(f"(?:[{text}])") @re_escape - def range(self, *args): - from_tos = [args[i : i + 2] for i in range(0, len(args), 2)] - return self.add("([%s])" % "".join(["-".join(i) for i in from_tos])) + @beartype + def not_any_of(self, text: CharClassOrChars) -> Verbex: + return self.__add(f"(?:[^{text}])") - def tab(self): - return self.add(r"\t") + # no text input - def word(self): - return self.add(r"(\w+)") + def anything(self) -> Verbex: + return self.__add(".*") - def OR(self, value=None): - """ `or` is a python keyword so we use `OR` instead. """ - self.add("|") - return self.find(value) if value else self + def asfew(self) -> Verbex: + return self.__add("?") - def replace(self, string, repl): - return self.sub(repl, string) + @beartype + def range(self, start: int, end: int) -> Verbex: + return self.__add( + "(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")" + ) - # --------------- modifiers ------------------------ + def word(self) -> Verbex: + return self.__add("(\\b\\w+\\b)") - # no global option. It depends on which method - # you called on the regex object. + # # --------------- modifiers ------------------------ - def with_any_case(self, value=False): - self.modifiers["I"] = re.I if value else 0 + def with_any_case(self) -> Verbex: + self._modifiers |= re.IGNORECASE return self - def search_one_line(self, value=False): - self.modifiers["M"] = re.M if value else 0 + def search_by_line(self) -> Verbex: + self._modifiers |= re.MULTILINE return self - def with_ascii(self, value=False): - self.modifiers["A"] = re.A if value else 0 + def with_ascii(self) -> Verbex: + self._modifiers |= re.ASCII return self + + +# left over notes from original version +# def __getattr__(self, attr): +# """ any other function will be sent to the regex object """ +# regex = self.regex() +# return getattr(regex, attr) + +# def range(self, start: int, end: int) -> Verbex: +# # this was the original? method +# from_tos = [args[i : i + 2] for i in range(0, len(args), 2)] +# return self.__add("([%s])" % "".join(["-".join(i) for i in from_tos])) + +# def replace(self, string, repl): +# return self.sub(repl, string) + + +t = ( + # Verbex().maybe(Verbex().find("tet")) + Verbex().capture_group( + "test", + Verbex().find("what to find"), + ) + # Verbex() + # .with_any_case() + # .maybe("test") + # .find(RegexEnum.DIGIT) + # .any("test") + # .range(10, 20) +) +print(t) diff --git a/verbex.py b/verbex.py new file mode 100644 index 0000000..8c2d9ce --- /dev/null +++ b/verbex.py @@ -0,0 +1,41 @@ +from verbalexpressions import VerEx, re_escape + +verbal_expression = VerEx() +# Create an example of how to test for correctly formed URLs +verbal_expression = VerEx() +tester = ( + verbal_expression.start_of_line() + .find("http") + .maybe("s") + .find("://") + .maybe("www.") + .anything_but(" ") + .end_of_line() +) + +# # Create an example URL +# test_url = "https://www.google.com" + +# # Test if the URL is valid +# if tester.match(test_url): +# print "Valid URL" + +# # Print the generated regex +# print tester.source() # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ + + +# # Create a test string +# replace_me = "Replace bird with a duck" + +# # Create an expression that looks for the word "bird" +# expression = VerEx().find('bird') + +# # Execute the expression in VerEx +# result_VerEx = expression.replace(replace_me, 'duck') +# print result_VerEx + +# # Or we can compile and use the regular expression using re +# import re +# regexp = expression.compile() +# result_re = regexp.sub('duck', replace_me) +# print result_re From 8a6cb18b9ecce9ae2340cb57dccdf332e9d246c3 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 24 Apr 2022 15:19:11 -0400 Subject: [PATCH 02/90] removed test file --- verbex.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 verbex.py diff --git a/verbex.py b/verbex.py deleted file mode 100644 index 8c2d9ce..0000000 --- a/verbex.py +++ /dev/null @@ -1,41 +0,0 @@ -from verbalexpressions import VerEx, re_escape - -verbal_expression = VerEx() -# Create an example of how to test for correctly formed URLs -verbal_expression = VerEx() -tester = ( - verbal_expression.start_of_line() - .find("http") - .maybe("s") - .find("://") - .maybe("www.") - .anything_but(" ") - .end_of_line() -) - -# # Create an example URL -# test_url = "https://www.google.com" - -# # Test if the URL is valid -# if tester.match(test_url): -# print "Valid URL" - -# # Print the generated regex -# print tester.source() # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ - - -# # Create a test string -# replace_me = "Replace bird with a duck" - -# # Create an expression that looks for the word "bird" -# expression = VerEx().find('bird') - -# # Execute the expression in VerEx -# result_VerEx = expression.replace(replace_me, 'duck') -# print result_VerEx - -# # Or we can compile and use the regular expression using re -# import re -# regexp = expression.compile() -# result_re = regexp.sub('duck', replace_me) -# print result_re From 5a32898975df5547fcc0bb7a003faaf0267ef787 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 1 May 2022 16:30:58 -0400 Subject: [PATCH 03/90] updated to python 3, using beartype, overal cleanup and improvement. --- .gitignore | 2 +- .pre-commit-config.yaml | 7 - .travis.yml | 3 - .vscode/settings.json | 16 + LICENSE.txt | 39 ++ README.md | 2 +- setup.py | 24 +- tests/test_verbal_expressions.py | 285 +++++++++++++++ tests/verbal_expressions_test.py | 263 -------------- verbalexpressions/__init__.py | 5 +- verbalexpressions/py.typed | 0 verbalexpressions/verbal_expressions.py | 461 ++++++++++++++++++------ 12 files changed, 715 insertions(+), 392 deletions(-) delete mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/settings.json create mode 100644 LICENSE.txt create mode 100644 tests/test_verbal_expressions.py delete mode 100644 tests/verbal_expressions_test.py create mode 100644 verbalexpressions/py.typed diff --git a/.gitignore b/.gitignore index ba0430d..c18dd8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -__pycache__/ \ No newline at end of file +__pycache__/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 877d718..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -repos: -- repo: https://github.com/ambv/black - rev: stable - hooks: - - id: black - args: [--line-length=79] - language_version: python3.6 diff --git a/.travis.yml b/.travis.yml index deafd0f..51219d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: python python: - - "2.7" - - "3.5" - "3.6" - "3.7" - "3.8" @@ -9,4 +7,3 @@ python: # command to run tests script: python setup.py test - diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..19a66dd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "test*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "cSpell.words": [ + "pylance", + "pyright", + "Verbex" + ] +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4b5ad82 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,39 @@ +Verbal Expressions +Copyright (C) 2022 Richard Broderick + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + + This file incorporates work covered by the following copyright and + permission notice: + + The MIT License (MIT) + + Copyright (c) 2017 jehna + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index d94fbc6..90fe3d5 100644 --- a/README.md +++ b/README.md @@ -68,5 +68,5 @@ print result python setup.py develop python setup.py test ``` -## Other implementations +## Other implementations You can view all implementations on [VerbalExpressions.github.io](http://VerbalExpressions.github.io) diff --git a/setup.py b/setup.py index e76b29e..021a3c6 100755 --- a/setup.py +++ b/setup.py @@ -2,21 +2,27 @@ setup( name="VerbalExpressions", - version="0.0.2", - description="Make difficult regular expressions easy! Python port of the awesome VerbalExpressions repo - https://github.com/jehna/VerbalExpressions", - long_description="Please see https://github.com/VerbalExpressions/PythonVerbalExpressions/blob/master/README.md for more information!", - author="Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer Raghuram, Kharms", + version="1.0.0", + description=( + "Make difficult regular expressions easy! Python port of the awesome" + " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" + ), + long_description=( + "Please see" + " https://github.com/VerbalExpressions/PythonVerbalExpressions/blob/master/README.md" + " for more information!" + ), + author=( + "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" + " Raghuram, Kharms, Richard Broderick" + ), license="MIT", url="https://github.com/VerbalExpressions/PythonVerbalExpressions", test_suite="tests", packages=["verbalexpressions"], - tests_require=["six"], - extras_require={"dev": ["pre-commit", "black"]}, classifiers=[ - "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", diff --git a/tests/test_verbal_expressions.py b/tests/test_verbal_expressions.py new file mode 100644 index 0000000..f9164e8 --- /dev/null +++ b/tests/test_verbal_expressions.py @@ -0,0 +1,285 @@ +# pyright: reportPrivateUsage=false +# flake8: noqa +# type: ignore +import re +import unittest + +from verbalexpressions import CharClass, SpecialChar, Verbex + + +class verbexTest(unittest.TestCase): + """Tests for verbal_expressions.py""" + + # def setUp(self): + # Verbex() = Verbex() + + # def tearDown(self): + # ... + # # Verbex() = None + # # self.exp = None + + def test_should_render_verbex_as_string(self): + self.assertEqual(str(Verbex()._add("^$")), "^$") # noqa + + def test_should_render_verbex_list_as_string(self): + self.assertEqual(str(Verbex()._add(["^", "[0-9]", "$"])), "^[0-9]$") # noqa + + def test_should_match_characters_in_range(self): + regex = Verbex().letter_range("a", "c").regex() + for character in ["a", "b", "c"]: + self.assertRegex(character, regex) + + def test_should_not_match_characters_outside_of_range(self): + regex = Verbex().letter_range("a", "c").regex() + self.assertNotRegex("d", regex) + + def test_should_match_start_of_line(self): + regex = Verbex().find(SpecialChar.START_OF_LINE).find("text ").regex() + self.assertRegex("text ", regex) + + def test_should_match_end_of_line(self): + regex = Verbex().find("test").find(SpecialChar.END_OF_LINE).regex() + self.assertRegex("IGNORE test", regex) + + def test_should_match_anything(self): + regex = Verbex().anything().regex() + self.assertIsNotNone(re.fullmatch(regex, "!@#$%¨&*()__+{}")) + + def test_should_match_anything_but_specified_element_when_element_is_not_found( # noqa: E501 + self, + ): + regex = Verbex().anything_but("X").find(" Files").regex() + self.assertRegex("Y Files", regex) + self.assertNotRegex("X Files", regex) + + def test_should_not_match_anything_but_specified_element_when_specified_element_is_found( # noqa: E501 + self, + ): + regex = Verbex().anything_but("X").regex() + self.assertRegex("Y Files", regex) + self.assertNotRegex("X", regex) + + def test_should_find_element(self): + regex = Verbex().find("Wally").regex() + self.assertRegex("Wally", regex) + self.assertNotRegex("Nally", regex) + + def test_should_not_find_missing_element(self): + regex = Verbex().find("Wally").regex() + self.assertNotRegex("Wall-e", regex) + + def test_should_match_when_maybe_element_is_present(self): + regex = ( + Verbex() + .start_of_line() + .find("Python2.") + .maybe("7") + .end_of_line() + .regex() # + ) + self.assertRegex("Python2.7", regex) + + def test_should_match_when_maybe_element_is_missing(self): + regex = ( + Verbex() + .start_of_line() + .find("Python2.") + .maybe("7") + .end_of_line() + .regex() # + ) + self.assertRegex("Python2.", regex) + + def test_should_match_on_any_when_element_is_found(self): + regex = ( + Verbex() + .start_of_line() + .any_of("Q") + .anything() + .end_of_line() + .regex() # E501 # + ) + self.assertRegex("Query", regex) + + def test_should_not_match_on_any_when_element_is_not_found(self): + regex = ( + Verbex() + .start_of_line() + .any_of("Q") + .anything() + .end_of_line() + .regex() # E501 # + ) + self.assertNotRegex("W", regex) + + def test_should_match_when_line_break_present(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .line_break() + .anything() + .end_of_line() + .regex() + ) + self.assertRegex("Marco \n Polo", regex) + self.assertNotRegex("Marco Polo", regex) + + def test_should_match_when_line_break_and_carriage_return_present(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .line_break() + .anything() + .end_of_line() + .regex() + ) + self.assertRegex("Marco \r\n Polo", regex) + + def test_should_not_match_when_line_break_is_missing(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .line_break() + .anything() + .end_of_line() + .regex() + ) + self.assertNotRegex("Marco Polo", regex) + + def test_should_match_when_tab_present(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .as_few() + .find("!") + .tab() + .end_of_line() + .regex() # E501 # + ) + self.assertRegex("One tab only!\t", regex) + self.assertNotRegex("One tab only!\t\t", regex) + + def test_should_not_match_when_tab_is_missing(self): + regex = Verbex().start_of_line().anything().tab().end_of_line().regex() + self.assertNotRegex("No tab here", regex) + + def test_should_match_when_word_present(self): + regex = Verbex().start_of_line().word().end_of_line().regex() + self.assertRegex("Oneword", regex) + + def test_not_match_when_two_words_are_present_instead_of_one(self): + regex = Verbex().start_of_line().word().end_of_line().regex() + self.assertNotRegex("Two words", regex) + + def test_should_match_when_or_condition_fulfilled(self): + regex = ( + Verbex() + .start_of_line() + .find("G") + .OR(Verbex().find("H")) + .anything() + .as_few() + .find("b") + .end_of_line() + .regex() + ) + self.assertRegex("Github", regex) + self.assertRegex("Hithub", regex) + + def test_should_not_match_when_or_condition_not_fulfilled(self): + regex = ( + Verbex() + .start_of_line() + .find("G") + .OR(Verbex().find("H")) + .anything() + .as_few() + .find("b") + .end_of_line() + .regex() + ) + self.assertNotRegex("ithub", regex) + + def test_should_match_on_upper_case_when_lower_case_is_given_and_any_case( + self, + ): + regex = ( + Verbex() + .start_of_line() + .find("THOR") + .end_of_line() + .with_any_case() + .regex() # E501 # + ) + self.assertRegex("thor", regex) + + def test_should_not_match_on_upper_case_when_lower_case_is_given( + self, + ): + regex = Verbex().start_of_line().find("THOR").end_of_line().regex() + self.assertNotRegex("thor", regex) + + def test_should_match_multiple_lines(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .find("Pong") + .anything() + .end_of_line() + .search_by_line() + .regex() + ) + self.assertRegex("Ping \n Pong \n Ping", regex) + + def test_should_not_match_multiple_lines(self): + regex = ( + Verbex() + .start_of_line() + .anything() + .find("Pong") + .anything() + .end_of_line() + .regex() + ) + self.assertNotRegex("Ping \n Pong \n Ping", regex) + + def test_should_match_email_like(self): + regex = ( + Verbex() + .start_of_line() + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .then("@") + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .then(".") + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .end_of_line() + .regex() + ) + self.assertRegex("mail@mail.com", regex) + + def test_should_match_url(self): + regex = ( + Verbex() + .start_of_line() + .then("http") + .maybe("s") + .then("://") + .maybe("www.") + .word() + .then(".") + .word() + .maybe("/") + .end_of_line() + .regex() + ) + self.assertRegex("https://www.google.com/", regex) + self.assertNotRegex("htps://www.google.com/", regex) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/verbal_expressions_test.py b/tests/verbal_expressions_test.py deleted file mode 100644 index 4996b2f..0000000 --- a/tests/verbal_expressions_test.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- encoding: utf-8 -*- - -import unittest -import re - -import six - -import verbalexpressions - - -class VerExTest(unittest.TestCase): - """ Tests for verbal_expressions.py """ - - if six.PY3: - assertNotRegexpMatches = unittest.TestCase.assertNotRegex - - def setUp(self): - self.v = verbalexpressions.VerEx() - - def tearDown(self): - self.v = None - self.exp = None - - def test_should_render_verex_as_string(self): - self.assertEqual(str(self.v.add("^$")), "^$") - - def test_should_render_verex_list_as_string(self): - self.assertEqual(str(self.v.add(["^", "[0-9]", "$"])), "^[0-9]$") - - def test_should_match_characters_in_range(self): - self.exp = self.v.start_of_line().range("a", "c").regex() - for character in ["a", "b", "c"]: - six.assertRegex(self, character, self.exp) - - def test_should_not_match_characters_outside_of_range(self): - self.exp = self.v.start_of_line().range("a", "c").regex() - self.assertNotRegexpMatches("d", self.exp) - - def test_should_match_characters_in_extended_range(self): - self.exp = self.v.start_of_line().range("a", "b", "X", "Z").regex() - for character in ["a", "b"]: - six.assertRegex(self, character, self.exp) - for character in ["X", "Y", "Z"]: - six.assertRegex(self, character, self.exp) - - def test_should_not_match_characters_outside_of_extended_range(self): - self.exp = self.v.start_of_line().range("a", "b", "X", "Z").regex() - self.assertNotRegexpMatches("c", self.exp) - self.assertNotRegexpMatches("W", self.exp) - - def test_should_match_start_of_line(self): - self.exp = self.v.start_of_line().regex() - six.assertRegex(self, "text ", self.exp, "Not started :(") - - def test_should_match_end_of_line(self): - self.exp = self.v.start_of_line().end_of_line().regex() - six.assertRegex(self, "", self.exp, "It's not the end!") - - def test_should_match_anything(self): - self.exp = self.v.start_of_line().anything().end_of_line().regex() - six.assertRegex( - self, "!@#$%¨&*()__+{}", self.exp, "Not so anything..." - ) - - def test_should_match_anything_but_specified_element_when_element_is_not_found( - self - ): - self.exp = ( - self.v.start_of_line().anything_but("X").end_of_line().regex() - ) - six.assertRegex(self, "Y Files", self.exp, "Found the X!") - - def test_should_not_match_anything_but_specified_element_when_specified_element_is_found( - self - ): - self.exp = ( - self.v.start_of_line().anything_but("X").end_of_line().regex() - ) - self.assertNotRegexpMatches("VerEX", self.exp, "Didn't found the X :(") - - def test_should_find_element(self): - self.exp = self.v.start_of_line().find("Wally").end_of_line().regex() - six.assertRegex(self, "Wally", self.exp, "404! Wally not Found!") - - def test_should_not_find_missing_element(self): - self.exp = self.v.start_of_line().find("Wally").end_of_line().regex() - self.assertNotRegexpMatches("Wall-e", self.exp, "DAFUQ is Wall-e?") - - def test_should_match_when_maybe_element_is_present(self): - self.exp = ( - self.v.start_of_line() - .find("Python2.") - .maybe("7") - .end_of_line() - .regex() - ) - six.assertRegex(self, "Python2.7", self.exp, "Version doesn't match!") - - def test_should_match_when_maybe_element_is_missing(self): - self.exp = ( - self.v.start_of_line() - .find("Python2.") - .maybe("7") - .end_of_line() - .regex() - ) - six.assertRegex(self, "Python2.", self.exp, "Version doesn't match!") - - def test_should_match_on_any_when_element_is_found(self): - self.exp = ( - self.v.start_of_line().any("Q").anything().end_of_line().regex() - ) - six.assertRegex(self, "Query", self.exp, "No match found!") - - def test_should_not_match_on_any_when_element_is_not_found(self): - self.exp = ( - self.v.start_of_line().any("Q").anything().end_of_line().regex() - ) - self.assertNotRegexpMatches("W", self.exp, "I've found it!") - - def test_should_match_when_line_break_present(self): - self.exp = ( - self.v.start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - six.assertRegex(self, "Marco \n Polo", self.exp, "Give me a break!!") - - def test_should_match_when_line_break_and_carriage_return_present(self): - self.exp = ( - self.v.start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - six.assertRegex(self, "Marco \r\n Polo", self.exp, "Give me a break!!") - - def test_should_not_match_when_line_break_is_missing(self): - self.exp = ( - self.v.start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - self.assertNotRegexpMatches( - "Marco Polo", self.exp, "There's a break here!" - ) - - def test_should_match_when_tab_present(self): - self.exp = ( - self.v.start_of_line().anything().tab().end_of_line().regex() - ) - six.assertRegex(self, "One tab only ", self.exp, "No tab here!") - - def test_should_not_match_when_tab_is_missing(self): - self.exp = ( - self.v.start_of_line().anything().tab().end_of_line().regex() - ) - self.assertFalse( - re.match(self.exp, "No tab here"), "There's a tab here!" - ) - - def test_should_match_when_word_present(self): - self.exp = ( - self.v.start_of_line().anything().word().end_of_line().regex() - ) - six.assertRegex(self, "Oneword", self.exp, "Not just a word!") - - def test_not_match_when_two_words_are_present_instead_of_one(self): - self.exp = ( - self.v.start_of_line().anything().tab().end_of_line().regex() - ) - self.assertFalse( - re.match(self.exp, "Two words"), "I've found two of them" - ) - - def test_should_match_when_or_condition_fulfilled(self): - self.exp = ( - self.v.start_of_line() - .anything() - .find("G") - .OR() - .find("h") - .end_of_line() - .regex() - ) - six.assertRegex(self, "Github", self.exp, "Octocat not found") - - def test_should_not_match_when_or_condition_not_fulfilled(self): - self.exp = ( - self.v.start_of_line() - .anything() - .find("G") - .OR() - .find("h") - .end_of_line() - .regex() - ) - self.assertFalse(re.match(self.exp, "Bitbucket"), "Bucket not found") - - def test_should_match_on_upper_case_when_lower_case_is_given_and_any_case_is_true( - self - ): - self.exp = ( - self.v.start_of_line() - .find("THOR") - .end_of_line() - .with_any_case(True) - .regex() - ) - six.assertRegex(self, "thor", self.exp, "Upper case Thor, please!") - - def test_should_match_multiple_lines(self): - self.exp = ( - self.v.start_of_line() - .anything() - .find("Pong") - .anything() - .end_of_line() - .search_one_line(True) - .regex() - ) - six.assertRegex( - self, "Ping \n Pong \n Ping", self.exp, "Pong didn't answer" - ) - - def test_should_match_email_address(self): - self.exp = ( - self.v.start_of_line() - .word() - .then("@") - .word() - .then(".") - .word() - .end_of_line() - .regex() - ) - six.assertRegex(self, "mail@mail.com", self.exp, "Not a valid email") - - def test_should_match_url(self): - self.exp = ( - self.v.start_of_line() - .then("http") - .maybe("s") - .then("://") - .maybe("www.") - .word() - .then(".") - .word() - .maybe("/") - .end_of_line() - .regex() - ) - six.assertRegex( - self, "https://www.google.com/", self.exp, "Not a valid email" - ) diff --git a/verbalexpressions/__init__.py b/verbalexpressions/__init__.py index 233a1e0..4a47efb 100644 --- a/verbalexpressions/__init__.py +++ b/verbalexpressions/__init__.py @@ -1 +1,4 @@ -from .verbal_expressions import VerbEx, re_escape +from .verbal_expressions import CharClass as CharClass +from .verbal_expressions import SpecialChar as SpecialChar +from .verbal_expressions import Verbex as Verbex +from .verbal_expressions import re_escape as re_escape diff --git a/verbalexpressions/py.typed b/verbalexpressions/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index eb65d10..a39f9d6 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -1,89 +1,117 @@ +"""Generate regular expressions from an easier fluent verbal form.""" from __future__ import annotations import re from enum import Enum from functools import wraps -from typing import Protocol +from typing import Pattern, Protocol, TypeAlias, TypeVar + +from beartype import beartype # type: ignore +from beartype.typing import ( # type: ignore + Any, + Callable, + Dict, + Iterator, + List, + Optional, + ParamSpec, + Tuple, + Union, + cast, + runtime_checkable, +) +from beartype.vale import Is # type: ignore try: - from beartype import beartype # type: ignore - from beartype.typing import ( # type: ignore - Any, - Callable, - Dict, - Iterator, - List, - Optional, - ParamSpec, - Tuple, - TypeVar, - Union, - cast, - runtime_checkable, - ) + from typing import Annotated # <--------------- if Python ≥ 3.9.0 except ModuleNotFoundError: - from typing import ( - Any, - Callable, - Dict, - Iterator, - List, - Optional, - ParamSpec, - Tuple, - TypeVar, - Union, - cast, - runtime_checkable, - ) + from typing_extensions import Annotated # type: ignore # <--- if Python < 3.9.0 + - __P = ParamSpec("__P") - __R = TypeVar("__R") +def _string_len_is_1(text: object) -> bool: + return isinstance(text, str) and len(text) == 1 - def noop_dec(func: Callable[__P, __R]) -> Callable[__P, __R]: - return func - beartype = noop_dec # type: ignore +Char = Annotated[str, Is[_string_len_is_1]] -P = ParamSpec("P") -R = TypeVar("R") +P = ParamSpec("P") # noqa VNE001 +R = TypeVar("R") # noqa VNE001 # work around for bug https://github.com/python/mypy/issues/12660 +# fixed in next version of mypy @runtime_checkable class HasIter(Protocol): + """Workaround for mypy P.args.""" + def __iter__(self) -> Iterator[Any]: + """Object can be iterated. + + Yields: + Next object. + """ ... # work around for bug https://github.com/python/mypy/issues/12660 +# fixed in next version of mypy @runtime_checkable class HasItems(Protocol): + """Workaround for mypy P.kwargs.""" + def items(self) -> Tuple[str, Any]: + """Object has items method. + + Returns: + The dict of items. + """ ... class EscapedText(str): - def __new__(cls, value: str): + """Text that has been escaped for regex. + + Arguments: + str -- Extend the string class. + """ + + def __new__(cls, value: str) -> EscapedText: + """Return a escaped regex string. + + Arguments: + value -- the string to escape + + Returns: + _description_ + """ return str.__new__(cls, re.escape(value)) def re_escape(func: Callable[P, R]) -> Callable[P, R]: + """Automatically escape any string parameters as EscapedText. + + Arguments: + func -- The function to decorate. + + Returns: + The decorated function. + """ + @wraps(func) - def inner(*args: P.args, **kwargs: P.kwargs) -> R: + def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore escaped_args: List[Any] = [] escaped_kwargs: Dict[str, Any] = {} for arg in cast(HasIter, args): - if isinstance(arg, str): - escaped_args.append(EscapedText(re.escape(arg))) + if not isinstance(arg, EscapedText) and isinstance(arg, str): + escaped_args.append(EscapedText(arg)) else: escaped_args.append(arg) arg_k: str arg_v: Any for arg_k, arg_v in cast(HasItems, kwargs).items(): - if isinstance(arg_v, str): - escaped_kwargs[arg_k] = EscapedText(re.escape(str(arg_v))) + if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): + escaped_kwargs[arg_k] = EscapedText(str(arg_v)) else: escaped_kwargs[arg_k] = arg_v return func(*escaped_args, **escaped_kwargs) # type: ignore @@ -92,6 +120,12 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> R: class CharClass(Enum): + """Enum of character classes in regex. + + Arguments: + Enum -- Extends the Enum class. + """ + DIGIT = "\\d" LETTER = "\\w" UPPERCASE_LETTER = "\\u" @@ -99,48 +133,78 @@ class CharClass(Enum): WHITESPACE = "\\s" TAB = "\\t" - def __str__(self): + def __str__(self) -> str: + """To string method based on Enum value. + + Returns: + value of Enum + """ return self.value class SpecialChar(Enum): + """Enum of special charaters, shorthand. + + Arguments: + Enum -- Extends the Enum class. + """ + # does not work / should not be used in [ ] LINEBREAK = "(\\n|(\\r\\n))" START_OF_LINE = "^" END_OF_LINE = "$" + TAB = "\t" - def __str__(self): + def __str__(self) -> str: + """To string for special chars enum. + + Returns: + Return value of enum as string. + """ return self.value -CharClassOrChars = Union[str, CharClass] -EscapedCharClassOrSpecial = Union[str, CharClass, SpecialChar] -VerbexEscapedCharClassOrSpecial = Union["Verbex", EscapedCharClassOrSpecial] +CharClassOrChars: TypeAlias = Union[str, CharClass] +EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] +VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] class Verbex: """ VerbalExpressions class. + the following methods do not try to match the original js lib! """ + EMPTY_REGEX_FLAG = re.RegexFlag(0) + @re_escape @beartype - def __init__(self, modifiers: re.RegexFlag = re.RegexFlag(0)): + def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): + """Create a Verbex object; setting any needed flags. + + Keyword Arguments: + modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) + """ # self._parts: List[str] = [text] self._parts: List[str] = [] self._modifiers = modifiers @property def modifiers(self) -> re.RegexFlag: + """Return the modifiers for this Verbex object. + + Returns: + The modifiers applied to this object. + """ return self._modifiers - def __str__(self): + def __str__(self) -> str: """Return regex string representation.""" return "".join(self._parts) @beartype - def __add(self, value: Union[str, List[str]]): + def _add(self, value: Union[str, List[str]]) -> Verbex: """ Append a transformed value to internal expression to be compiled. @@ -152,8 +216,8 @@ def __add(self, value: Union[str, List[str]]): self._parts.append(value) return self - def regex(self): - """get a regular expression object.""" + def regex(self) -> Pattern[str]: + """Get a regular expression object.""" return re.compile( str(self), self._modifiers, @@ -168,7 +232,7 @@ def _capture_group_with_name( name: str, text: VerbexEscapedCharClassOrSpecial, ) -> Verbex: - return self.__add(f"(?<{name}>{str(text)})") + return self._add(f"(?<{name}>{str(text)})") @re_escape @beartype @@ -176,18 +240,30 @@ def _capture_group_without_name( self, text: VerbexEscapedCharClassOrSpecial, ) -> Verbex: - return self.__add(f"({str(text)})") + return self._add(f"({str(text)})") @re_escape @beartype def capture_group( self, /, - name_or_text: Union[ - Optional[str], VerbexEscapedCharClassOrSpecial - ] = None, + name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, text: Optional[VerbexEscapedCharClassOrSpecial] = None, ) -> Verbex: + """Create a capture group. + + Name is optional if not specified then the first argument is the text. + + Keyword Arguments: + name_or_text -- The name of the group / text to search for (default: {None}) + text -- The text to search for (default: {None}) + + Raises: + ValueError: If name is specified then text must be as well. + + Returns: + Verbex with added capture group. + """ if name_or_text is not None: if text is None: _text = name_or_text @@ -198,96 +274,284 @@ def capture_group( @re_escape @beartype - def OR(self, text: VerbexEscapedCharClassOrSpecial): # noqa N802 - """`or` is a python keyword so we use `OR` instead.""" - self.__add("|") - self.find(text) + def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 + """`or` is a python keyword so we use `OR` instead. + + Arguments: + text -- Text to find or a Verbex object. + + Returns: + Modified Verbex object. + """ + return self._add("|").find(text) @re_escape @beartype def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - return self.__add(f"(?:{str(text)})*") + """Find the text or Verbex object zero or more times. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)})*") @re_escape @beartype def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - return self.__add(f"(?:{str(text)})+") + """Find the text or Verbex object one or more times. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)})+") @re_escape @beartype - def n_times(self, text: VerbexEscapedCharClassOrSpecial, n: int) -> Verbex: - return self.__add(f"(?:{str(text)}){{{n}}}") + def n_times( + self, + text: VerbexEscapedCharClassOrSpecial, + n: int, # noqa: VNE001 + ) -> Verbex: + """Find the text or Verbex object n or more times. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)}){{{n}}}") @re_escape @beartype def n_times_or_more( - self, text: VerbexEscapedCharClassOrSpecial, n: int + self, + text: VerbexEscapedCharClassOrSpecial, + n: int, # noqa: VNE001 ) -> Verbex: - return self.__add(f"(?:{str(text)}){{{n},}}") + """Find the text or Verbex object at least n times. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)}){{{n},}}") @re_escape @beartype def n_to_m_times( - self, text: VerbexEscapedCharClassOrSpecial, n: int, m: int + self, + text: VerbexEscapedCharClassOrSpecial, + n: int, # noqa: VNE001 + m: int, # noqa: VNE001 ) -> Verbex: - return self.__add(f"(?:{str(text)}){{{n},{m}}}") + """Find the text or Verbex object between n and m times. - @re_escape - def anything_but(self, text: EscapedCharClassOrSpecial): - return self.__add(f"[^{text}]*") + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)}){{{n},{m}}}") @re_escape @beartype def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - # if isinstance(text, Verbex): - # return self.__add(f"(?:{str(text)})?") - return self.__add(f"(?:{str(text)})?") + """Possibly find the text / Verbex object. + + Arguments: + text -- The text / Verbex object to possibly find. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:{str(text)})?") @re_escape @beartype def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - return self.__add(str(text)) + """Find the text or Verbex object. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self._add(str(text)) + + @re_escape + @beartype + def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Synonym for find. + + Arguments: + text -- The text / Verbex object to look for. + + Returns: + Modified Verbex object. + """ + return self.find(text) # only allow CharclassOrChars @re_escape @beartype - def any_of(self, text: CharClassOrChars) -> Verbex: - return self.__add(f"(?:[{text}])") + def any_of(self, chargroup: CharClassOrChars) -> Verbex: + """Find anything in this group of chars or char class. + + Arguments: + text -- The characters to look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:[{chargroup}])") @re_escape @beartype def not_any_of(self, text: CharClassOrChars) -> Verbex: - return self.__add(f"(?:[^{text}])") + """Find anything but this group of chars or char class. + + Arguments: + text -- The characters to not look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"(?:[^{text}])") + + @re_escape + def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: + """Find anything one or more times but this group of chars or char class. + + Arguments: + text -- The characters to not look for. + + Returns: + Modified Verbex object. + """ + return self._add(f"[^{chargroup}]+") # no text input + def start_of_line(self) -> Verbex: + """Find the start of the line. + + Returns: + Modified Verbex object. + """ + return self.find(SpecialChar.START_OF_LINE) + + def end_of_line(self) -> Verbex: + """Find the end of the line. + + Returns: + Modified Verbex object. + """ + return self.find(SpecialChar.END_OF_LINE) + + def line_break(self) -> Verbex: + """Find a line break. + + Returns: + Modified Verbex object. + """ + return self.find(SpecialChar.LINEBREAK) + + def tab(self) -> Verbex: + """Find a tab. + + Returns: + Modified Verbex object. + """ + return self.find(SpecialChar.TAB) + def anything(self) -> Verbex: - return self.__add(".*") + """Find anything one or more time. - def asfew(self) -> Verbex: - return self.__add("?") + Returns: + Modified Verbex object. + """ + return self._add(".+") + + def as_few(self) -> Verbex: + """Modify previous search to not be greedy. + + Returns: + Modified Verbex object. + """ + return self._add("?") @beartype - def range(self, start: int, end: int) -> Verbex: - return self.__add( - "(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")" - ) + def number_range(self, start: int, end: int) -> Verbex: + """Generate a range of numbers. + + Arguments: + start -- Start of the range + end -- End of the range + + Returns: + Modified Verbex object. + """ + return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") + + @beartype + def letter_range(self, start: Char, end: Char) -> Verbex: + """Generate a range of letters. + + Arguments: + start -- Start of the range + end -- End of the range + + Returns: + Modified Verbex object. + """ + return self._add(f"[{start}-{end}]") def word(self) -> Verbex: - return self.__add("(\\b\\w+\\b)") + """Find a word on word boundary. + + Returns: + Modified Verbex object. + """ + return self._add("(\\b\\w+\\b)") # # --------------- modifiers ------------------------ def with_any_case(self) -> Verbex: + """Modify Verbex object to be case insensitive. + + Returns: + Modified Verbex object. + """ self._modifiers |= re.IGNORECASE return self def search_by_line(self) -> Verbex: + """Search each line, ^ and $ match begining and end of line respectively. + + Returns: + Modified Verbex object. + """ self._modifiers |= re.MULTILINE return self def with_ascii(self) -> Verbex: + """Match ascii instead of unicode. + + Returns: + Modified Verbex object. + """ self._modifiers |= re.ASCII return self @@ -298,26 +562,9 @@ def with_ascii(self) -> Verbex: # regex = self.regex() # return getattr(regex, attr) -# def range(self, start: int, end: int) -> Verbex: -# # this was the original? method -# from_tos = [args[i : i + 2] for i in range(0, len(args), 2)] -# return self.__add("([%s])" % "".join(["-".join(i) for i in from_tos])) - # def replace(self, string, repl): # return self.sub(repl, string) -t = ( - # Verbex().maybe(Verbex().find("tet")) - Verbex().capture_group( - "test", - Verbex().find("what to find"), - ) - # Verbex() - # .with_any_case() - # .maybe("test") - # .find(RegexEnum.DIGIT) - # .any("test") - # .range(10, 20) -) -print(t) +if __name__ == "__main__": + pass From cb7ef0c03d30f111054aa15b7fff100e0c6fa0d6 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 1 May 2022 22:23:34 -0400 Subject: [PATCH 04/90] added look arounds --- tests/test_verbal_expressions.py | 20 +++++++++++ verbalexpressions/verbal_expressions.py | 48 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tests/test_verbal_expressions.py b/tests/test_verbal_expressions.py index f9164e8..a050a44 100644 --- a/tests/test_verbal_expressions.py +++ b/tests/test_verbal_expressions.py @@ -280,6 +280,26 @@ def test_should_match_url(self): self.assertRegex("https://www.google.com/", regex) self.assertNotRegex("htps://www.google.com/", regex) + def test_followed_by(self): + regex = Verbex().find("!").followed_by(":").regex() + self.assertRegex("!:", regex) + self.assertNotRegex("! :", regex) + + def test_not_followed_by(self): + regex = Verbex().find("!").not_followed_by(":").regex() + self.assertNotRegex("!:", regex) + self.assertRegex("! :", regex) + + def test_preceded_by(self): + regex = Verbex().preceded_by("!").find(":").regex() + self.assertRegex("!:", regex) + self.assertNotRegex("! :", regex) + + def test_not_preceded_by(self): + regex = Verbex().not_preceded_by("!").find(":").regex() + self.assertNotRegex("!:", regex) + self.assertRegex("! :", regex) + if __name__ == "__main__": unittest.main() diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index a39f9d6..dc8382e 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -402,6 +402,54 @@ def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """ return self.find(text) + @re_escape + @beartype + def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Match if string is followed by text. + + Positive lookahead + + Returns: + Modified Verbex object. + """ + return self._add(f"(?={text})") + + @re_escape + @beartype + def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Match if string is not followed by text. + + Negative lookahead + + Returns: + Modified Verbex object. + """ + return self._add(f"(?!{text})") + + @re_escape + @beartype + def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Match if string is not preceded by text. + + Positive lookbehind + + Returns: + Modified Verbex object. + """ + return self._add(f"(?<={text})") + + @re_escape + @beartype + def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Match if string is not preceded by text. + + Negative Lookbehind + + Returns: + Modified Verbex object. + """ + return self._add(f"(? Date: Sun, 1 May 2022 22:29:30 -0400 Subject: [PATCH 05/90] update readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 90fe3d5..12fb7f4 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ pip install VerbalExpressions ``` ## Usage ```python -from verbalexpressions import VerEx -verbal_expression = VerEx() +from verbalexpressions import Verbex +verbal_expression = Verbex() ``` ## Examples ### Testing if we have a valid URL ```python # Create an example of how to test for correctly formed URLs -verbal_expression = VerEx() +verbal_expression = Verbex() tester = (verbal_expression. start_of_line(). find('http'). @@ -45,11 +45,11 @@ print tester.source() # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ replace_me = "Replace bird with a duck" # Create an expression that looks for the word "bird" -expression = VerEx().find('bird') +expression = Verbex().find('bird') -# Execute the expression in VerEx -result_VerEx = expression.replace(replace_me, 'duck') -print result_VerEx +# Execute the expression in Verbex +result_Verbex = expression.replace(replace_me, 'duck') +print result_Verbex # Or we can compile and use the regular expression using re import re @@ -59,7 +59,7 @@ print result_re ``` ### Shorthand for string replace ```python -result = VerEx().find('red').replace('We have a red house', 'blue') +result = Verbex().find('red').replace('We have a red house', 'blue') print result ``` From 963c7b13c619ed440b8583a35525ce4951eb56ef Mon Sep 17 00:00:00 2001 From: rbroderi Date: Wed, 4 May 2022 20:38:25 -0400 Subject: [PATCH 06/90] Create main.yml --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0a32f79 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,37 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + + - name: Run .travis.yml build script + uses: ktomk/run-travis-yml@v1 + with: + file: .travis.yml + steps: | + install + script + allow-failure: false + env: + TRAVIS_PHP_VERSION: ${{ matrix.php-versions }} From 7234a88e625f50b44f299d2e3e6f084dacfe2e32 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 20:45:14 -0400 Subject: [PATCH 07/90] fix missing typealias for python 3.8: --- verbalexpressions/verbal_expressions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index dc8382e..00e10ef 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -4,7 +4,13 @@ import re from enum import Enum from functools import wraps -from typing import Pattern, Protocol, TypeAlias, TypeVar + +try: + from typing import TypeAlias +except ModuleNotFoundError: + from typing_extensions import TypeAlias + +from typing import Pattern, Protocol, TypeVar from beartype import beartype # type: ignore from beartype.typing import ( # type: ignore From fa247a0d37c304f01e939331ae30d1a047bb6e0e Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 20:51:07 -0400 Subject: [PATCH 08/90] condense python version dependent imports --- verbalexpressions/verbal_expressions.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index 00e10ef..ac48443 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -6,9 +6,9 @@ from functools import wraps try: - from typing import TypeAlias + from typing import Annotated, TypeAlias # <--------------- if Python ≥ 3.9.0 except ModuleNotFoundError: - from typing_extensions import TypeAlias + from typing_extensions import TypeAlias, Annotated # type: ignore # <--- if Python < 3.9.0 from typing import Pattern, Protocol, TypeVar @@ -28,11 +28,6 @@ ) from beartype.vale import Is # type: ignore -try: - from typing import Annotated # <--------------- if Python ≥ 3.9.0 -except ModuleNotFoundError: - from typing_extensions import Annotated # type: ignore # <--- if Python < 3.9.0 - def _string_len_is_1(text: object) -> bool: return isinstance(text, str) and len(text) == 1 From c74049aae4ca41f3f035846bdf3b01ca3ff1caea Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 20:52:52 -0400 Subject: [PATCH 09/90] condense python version dependent imports --- verbalexpressions/verbal_expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index ac48443..366d669 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -7,7 +7,7 @@ try: from typing import Annotated, TypeAlias # <--------------- if Python ≥ 3.9.0 -except ModuleNotFoundError: +except (ModuleNotFoundError, ImportError): from typing_extensions import TypeAlias, Annotated # type: ignore # <--- if Python < 3.9.0 from typing import Pattern, Protocol, TypeVar From c63967025ebc339c7b8a91c69af4707f776418aa Mon Sep 17 00:00:00 2001 From: rbroderi Date: Wed, 4 May 2022 20:58:56 -0400 Subject: [PATCH 10/90] Update main.yml --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a32f79..a3ecc00 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,8 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 - + - name: Install xmllint + run: pip install typing-extensions - name: Run .travis.yml build script uses: ktomk/run-travis-yml@v1 with: From 1f461e52cfe050ea28c5ca3f7e54b866fe199fa8 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Wed, 4 May 2022 21:05:50 -0400 Subject: [PATCH 11/90] Update main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3ecc00..0383dc3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 - name: Install xmllint - run: pip install typing-extensions + run: pip install typing-extensions beartype - name: Run .travis.yml build script uses: ktomk/run-travis-yml@v1 with: From d38fe3c6f870df8487f32b7647855bfecc878ad9 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 21:08:38 -0400 Subject: [PATCH 12/90] condense python version dependent imports --- verbalexpressions/verbal_expressions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/verbalexpressions/verbal_expressions.py b/verbalexpressions/verbal_expressions.py index 366d669..1287218 100644 --- a/verbalexpressions/verbal_expressions.py +++ b/verbalexpressions/verbal_expressions.py @@ -6,9 +6,13 @@ from functools import wraps try: - from typing import Annotated, TypeAlias # <--------------- if Python ≥ 3.9.0 + from typing import ( # <--------------- if Python ≥ 3.9.0 + Annotated, + ParamSpec, + TypeAlias, + ) except (ModuleNotFoundError, ImportError): - from typing_extensions import TypeAlias, Annotated # type: ignore # <--- if Python < 3.9.0 + from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 from typing import Pattern, Protocol, TypeVar @@ -20,7 +24,6 @@ Iterator, List, Optional, - ParamSpec, Tuple, Union, cast, From 6f8f1c5ffac19a54a7038031a517c1a59c4c825a Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 21:25:17 -0400 Subject: [PATCH 13/90] adding github actions badge --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 12fb7f4..3d88b26 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ PythonVerbalExpressions ======================= -[![Build Status](https://travis-ci.org/VerbalExpressions/PythonVerbalExpressions.svg?branch=master)](https://travis-ci.org/VerbalExpressions/PythonVerbalExpressions) +![Build Status](https://github.com/rbroderi/PythonVerbalExpressions/actions/workflows/main.yml/badge.svg?event=push) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) ## Installation @@ -57,11 +57,6 @@ regexp = expression.compile() result_re = regexp.sub('duck', replace_me) print result_re ``` -### Shorthand for string replace -```python -result = Verbex().find('red').replace('We have a red house', 'blue') -print result -``` ## Developer setup : running the tests ```bash From 9f4a8e431d32661638d0ea9648fe37adfa43c627 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 4 May 2022 21:27:11 -0400 Subject: [PATCH 14/90] updated readme --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 3d88b26..74f3e16 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,7 @@ replace_me = "Replace bird with a duck" # Create an expression that looks for the word "bird" expression = Verbex().find('bird') -# Execute the expression in Verbex -result_Verbex = expression.replace(replace_me, 'duck') -print result_Verbex - -# Or we can compile and use the regular expression using re +# Compile and use the regular expression using re import re regexp = expression.compile() result_re = regexp.sub('duck', replace_me) From 09a59fc436457fbab1099806c0533aac0d477401 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sat, 7 May 2022 20:25:24 -0400 Subject: [PATCH 15/90] Create GPL_LICENSE.txt --- GPL_LICENSE.txt | 674 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 GPL_LICENSE.txt diff --git a/GPL_LICENSE.txt b/GPL_LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/GPL_LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 8c8e673e89ce7950c5c1966465014e8d58d21aa1 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 20:47:10 -0400 Subject: [PATCH 16/90] change name and structure as fork --- GPL_LICENSE.txt => GPLv3_LICENSE.txt | 0 MANIFEST.IN | 3 +++ setup.py | 11 ++++++----- {verbalexpressions => verbex}/__init__.py | 0 {verbalexpressions => verbex}/py.typed | 0 .../verbal_expressions.py => verbex/verbex.py | 0 6 files changed, 9 insertions(+), 5 deletions(-) rename GPL_LICENSE.txt => GPLv3_LICENSE.txt (100%) create mode 100644 MANIFEST.IN rename {verbalexpressions => verbex}/__init__.py (100%) rename {verbalexpressions => verbex}/py.typed (100%) rename verbalexpressions/verbal_expressions.py => verbex/verbex.py (100%) diff --git a/GPL_LICENSE.txt b/GPLv3_LICENSE.txt similarity index 100% rename from GPL_LICENSE.txt rename to GPLv3_LICENSE.txt diff --git a/MANIFEST.IN b/MANIFEST.IN new file mode 100644 index 0000000..148cc7d --- /dev/null +++ b/MANIFEST.IN @@ -0,0 +1,3 @@ +include verbex/py.typed +include LICENSE.TXT +include GPLv3_LICESNSE.TXT \ No newline at end of file diff --git a/setup.py b/setup.py index 021a3c6..5ccd4b4 100755 --- a/setup.py +++ b/setup.py @@ -1,25 +1,25 @@ from setuptools import setup setup( - name="VerbalExpressions", + name="Verbex", version="1.0.0", description=( - "Make difficult regular expressions easy! Python port of the awesome" + "Make difficult regular expressions easy! Python fork based on of the awesome" " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" ), long_description=( "Please see" - " https://github.com/VerbalExpressions/PythonVerbalExpressions/blob/master/README.md" + " https://github.com/rbroderi/Verbex/blob/master/README.md" " for more information!" ), author=( "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" " Raghuram, Kharms, Richard Broderick" ), - license="MIT", + license="GPLv3", url="https://github.com/VerbalExpressions/PythonVerbalExpressions", test_suite="tests", - packages=["verbalexpressions"], + packages=["verbex"], classifiers=[ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python", @@ -29,4 +29,5 @@ "Topic :: Software Development :: Libraries", "Topic :: Text Processing", ], + include_package_data=True, ) diff --git a/verbalexpressions/__init__.py b/verbex/__init__.py similarity index 100% rename from verbalexpressions/__init__.py rename to verbex/__init__.py diff --git a/verbalexpressions/py.typed b/verbex/py.typed similarity index 100% rename from verbalexpressions/py.typed rename to verbex/py.typed diff --git a/verbalexpressions/verbal_expressions.py b/verbex/verbex.py similarity index 100% rename from verbalexpressions/verbal_expressions.py rename to verbex/verbex.py From 628509b12a779bee810460bdc6d860764d8a4acd Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 20:49:28 -0400 Subject: [PATCH 17/90] update name in init.py --- verbex/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/verbex/__init__.py b/verbex/__init__.py index 4a47efb..fa3bafc 100644 --- a/verbex/__init__.py +++ b/verbex/__init__.py @@ -1,4 +1,3 @@ -from .verbal_expressions import CharClass as CharClass -from .verbal_expressions import SpecialChar as SpecialChar -from .verbal_expressions import Verbex as Verbex -from .verbal_expressions import re_escape as re_escape +from .verbex import CharClass as CharClass +from .verbex import SpecialChar as SpecialChar +from .verbex import Verbex as Verbex From eb3ce5398bf7d2b5fe200e8907cb4071552001ce Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 20:51:20 -0400 Subject: [PATCH 18/90] update name in init.py --- tests/{test_verbal_expressions.py => test_verbex.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/{test_verbal_expressions.py => test_verbex.py} (99%) diff --git a/tests/test_verbal_expressions.py b/tests/test_verbex.py similarity index 99% rename from tests/test_verbal_expressions.py rename to tests/test_verbex.py index a050a44..9b7f81c 100644 --- a/tests/test_verbal_expressions.py +++ b/tests/test_verbex.py @@ -4,7 +4,7 @@ import re import unittest -from verbalexpressions import CharClass, SpecialChar, Verbex +from verbex import CharClass, SpecialChar, Verbex class verbexTest(unittest.TestCase): From d48c248e8acf27b29467047bd2e035e42c5723ca Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sat, 7 May 2022 20:57:41 -0400 Subject: [PATCH 19/90] Update README.md --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 74f3e16..94abab5 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -PythonVerbalExpressions -======================= +Verbex: Python verbal based regular expressions +================================================ -![Build Status](https://github.com/rbroderi/PythonVerbalExpressions/actions/workflows/main.yml/badge.svg?event=push) +![Build Status](https://github.com/rbroderi/Verbex/actions/workflows/main.yml/badge.svg?event=push) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) ## Installation ```bash -pip install VerbalExpressions +pip install Verbex ``` ## Usage ```python -from verbalexpressions import Verbex -verbal_expression = Verbex() +from verbex import Verbex +verbex = Verbex() ``` ## Examples ### Testing if we have a valid URL ```python # Create an example of how to test for correctly formed URLs -verbal_expression = Verbex() -tester = (verbal_expression. +verbex = Verbex() +tester = (verbex. start_of_line(). find('http'). maybe('s'). @@ -33,11 +33,11 @@ tester = (verbal_expression. test_url = "https://www.google.com" # Test if the URL is valid -if tester.match(test_url): - print "Valid URL" +if re.match(test_url.regex,test_url): + print("Valid URL") # Print the generated regex -print tester.source() # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ +print(tester) # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ ``` ### Replacing strings ```python @@ -51,7 +51,7 @@ expression = Verbex().find('bird') import re regexp = expression.compile() result_re = regexp.sub('duck', replace_me) -print result_re +print(result_re) ``` ## Developer setup : running the tests From ada781c7c7f854d72cbcc9f315fed8d4c31098b8 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:11:37 -0400 Subject: [PATCH 20/90] update setup.py --- .gitignore | 4 ++++ dist/Verbex-1.0.0.win-amd64.zip | Bin 0 -> 12709 bytes setup.py | 12 +++++++----- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 dist/Verbex-1.0.0.win-amd64.zip diff --git a/.gitignore b/.gitignore index c18dd8d..a0dcaf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ __pycache__/ +build/ +.mypy_cache/ +*.egg-info/ +.vscode/ \ No newline at end of file diff --git a/dist/Verbex-1.0.0.win-amd64.zip b/dist/Verbex-1.0.0.win-amd64.zip new file mode 100644 index 0000000000000000000000000000000000000000..0c3fa53a91fc9e507568fbaef876937471fe0957 GIT binary patch literal 12709 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 literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index 5ccd4b4..f2dbd38 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,10 @@ +from pathlib import Path + from setuptools import setup +SCRIPT_ROOT = Path(__file__).parent +long_description = (SCRIPT_ROOT / "README.md").read_text() + setup( name="Verbex", version="1.0.0", @@ -7,11 +12,8 @@ "Make difficult regular expressions easy! Python fork based on of the awesome" " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" ), - long_description=( - "Please see" - " https://github.com/rbroderi/Verbex/blob/master/README.md" - " for more information!" - ), + long_description=long_description, + long_description_content_type='text/markdown' author=( "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" " Raghuram, Kharms, Richard Broderick" From 9ddf7a8dabf6bda1953753328eee78b4d05d56da Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:13:25 -0400 Subject: [PATCH 21/90] update version to 1.0.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2dbd38..c6d64ca 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="Verbex", - version="1.0.0", + version="1.0.1", description=( "Make difficult regular expressions easy! Python fork based on of the awesome" " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" From ec5231fa276957a3310904cc6d9b126a75a3ffe6 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:17:27 -0400 Subject: [PATCH 22/90] push to pypi.org --- dist/Verbex-1.0.1.win-amd64.zip | Bin 0 -> 13388 bytes setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 dist/Verbex-1.0.1.win-amd64.zip diff --git a/dist/Verbex-1.0.1.win-amd64.zip b/dist/Verbex-1.0.1.win-amd64.zip new file mode 100644 index 0000000000000000000000000000000000000000..0bee37f29b091b2fae1fa6a8d1c2f1fad2907640 GIT binary patch literal 13388 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 literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index c6d64ca..a629f35 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" ), long_description=long_description, - long_description_content_type='text/markdown' + long_description_content_type="text/markdown", author=( "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" " Raghuram, Kharms, Richard Broderick" From 2950a5802632e2ae584e2f38cd63857e698a6740 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:19:56 -0400 Subject: [PATCH 23/90] update setup.py python versions --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index a629f35..209b868 100755 --- a/setup.py +++ b/setup.py @@ -28,6 +28,9 @@ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: Libraries", "Topic :: Text Processing", ], From b0f29e214d4144b14a42d678e697a30d062be2fd Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:20:44 -0400 Subject: [PATCH 24/90] updated travis.yml with python versions --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 51219d3..8622eeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ python: - "3.6" - "3.7" - "3.8" - - "3.9-dev" + - "3.9" + - "3.10" + - "3.11" # command to run tests script: python setup.py test From 1298138bab1e00f102e54a948073fa1d460704d6 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 21:36:04 -0400 Subject: [PATCH 25/90] updated manifest.in --- MANIFEST.IN | 2 +- dist/Verbex-1.0.1.win-amd64.zip | Bin 13388 -> 27015 bytes setup.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.IN b/MANIFEST.IN index 148cc7d..e0d237d 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,3 +1,3 @@ include verbex/py.typed include LICENSE.TXT -include GPLv3_LICESNSE.TXT \ No newline at end of file +include GPLv3_LICENSE.txt \ No newline at end of file diff --git a/dist/Verbex-1.0.1.win-amd64.zip b/dist/Verbex-1.0.1.win-amd64.zip index 0bee37f29b091b2fae1fa6a8d1c2f1fad2907640..55088e92eca8c181efa4d5880dcda14826c639fd 100644 GIT binary patch delta 15425 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 Date: Sat, 7 May 2022 23:23:27 -0400 Subject: [PATCH 26/90] added documentation --- dist/Verbex-1.0.0.win-amd64.zip | Bin 12709 -> 0 bytes ...n-amd64.zip => Verbex-1.0.2.win-amd64.zip} | Bin 27015 -> 27017 bytes dist/Verbex-1.0.3.win-amd64.zip | Bin 0 -> 27010 bytes html/verbex/index.html | 68 + html/verbex/verbex.html | 2290 +++++++++++++++++ setup.py | 4 +- 6 files changed, 2360 insertions(+), 2 deletions(-) delete mode 100644 dist/Verbex-1.0.0.win-amd64.zip rename dist/{Verbex-1.0.1.win-amd64.zip => Verbex-1.0.2.win-amd64.zip} (88%) create mode 100644 dist/Verbex-1.0.3.win-amd64.zip create mode 100644 html/verbex/index.html create mode 100644 html/verbex/verbex.html diff --git a/dist/Verbex-1.0.0.win-amd64.zip b/dist/Verbex-1.0.0.win-amd64.zip deleted file mode 100644 index 0c3fa53a91fc9e507568fbaef876937471fe0957..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12709 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 diff --git a/dist/Verbex-1.0.1.win-amd64.zip b/dist/Verbex-1.0.2.win-amd64.zip similarity index 88% rename from dist/Verbex-1.0.1.win-amd64.zip rename to dist/Verbex-1.0.2.win-amd64.zip index 55088e92eca8c181efa4d5880dcda14826c639fd..f7a260975d7feda6972cb000aae92ba03f975d77 100644 GIT binary patch delta 1418 zcmYk6dpOf;9KelS$`}cAtJE|1xy)s9i=1SHjpjCTnc5biMTKhOWXH*NvMF;(M_Fm^ zOlKk~45zszgjR+W%B|2UoS&ZOJm+Wvm-=d zJGK1&u}(ypD}yNvK>&TFP#8t8vAU_6T_U|2xfjPJjVk2db1UQ;Fl>rTYTEMmNi&w; zEu423GunD2gB+oHgr5HRX#x6nUu|K~&}Q0bJWO^G;RH@U>o1ZZnzbiE%TH66Tz(+H zZeCc6$Iupi8<$S;1wox#U|*nB8mT3zqTlv6P}IpK$qad%(RMtWSsSWC>2fUR2fOU? zh>o^&XQYkQq9%`GyL?Pwo)48G79kcZ(f;T!_TjkH5``Gq{(=l}KDouR%qoQZ?bB1F zp2+y`+q94U4cKB|K@h}giE+jjTU)bDDQF;Fce*s9!~+%sam(=uK3;t%#;PoNu_o#f z!jU(wvHo0C;Xl<|hk{ZHm%!{|sf*Aeudu66(dM3+Wis)J8%sYv4}UhdRm+;tK3gJ@ zJ8uqcdEal?^dP$8UQT#0#r8=W!~kOnYSmM9z@_+vxlD;0AtM>Vs=LD5Pk;zV)fCt- zF!f2v^s4V`MjPwm>j#S<;?LsiPl_N19KP;hwNhE0SU=THD_;uU9i^ks}=YQ9CfbNX*M^;$0q#&u;BRjIkE_>57A-F*%eL z`Wa&Gdx9I2f;V|7=)1zpQorHuds7`+Kw8R27<|GAS&Ww#*C)QQ3|d}YwI>(_+Ke>) z9&k9ugT4=G5dc5J$lk0ngE-t-0If@H+OE#`*aqG3R#_NL)E|3G^&^?6yexaY(j^q? zz^cIM`NJ)^f#f1aziTDfkZ(43B867Zxww2rv57;u@i4lNyhdzLPDDEBpYCnuB$)Z+PtYS@bPs#Xar)4F&Y9ZLxC;d%c za}M?5&+(`BTFub+o}!(BwmhcpEdErX`F4k?#jH~;y+Lr7?s@)w&4b8myYA9Q5L_sy z#}9FRBt|W6gjKQ)O^+SmyXm~Dy(>UsGF)&|It|O>1VZy^?w@`O72Jt8!3J2lgyfNE zCOKT6(p)~<@E17t!>VJV2-v|5lkQ*TvS*$-X|F3BY~nO#&ot9xWsqH7FK7nS1+1ar zgR$e^@FRQ zpPy-C@A?X=Jd-l{;neKcLL1^Wr^~VR_V_u?0zI1DJn`1<7;;~zY9uXirFvoF-C^f$ ze4mXq*cs{j=t4-fY1Q;b2uZg7JZ(;T3Z6VRo?3ue%Hwgh;F3bx4G1mxMD&)>_$SE#4-gUnRuZu>b%7 delta 1386 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 KIdbD4|NaNF7TQ+; literal 0 HcmV?d00001 diff --git a/html/verbex/index.html b/html/verbex/index.html new file mode 100644 index 0000000..fa7fe2b --- /dev/null +++ b/html/verbex/index.html @@ -0,0 +1,68 @@ + + + + + + +verbex API documentation + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/verbex/verbex.html b/html/verbex/verbex.html new file mode 100644 index 0000000..639b601 --- /dev/null +++ b/html/verbex/verbex.html @@ -0,0 +1,2290 @@ + + + + + + +verbex.verbex API documentation + + + + + + + + + + + +
+
+
+

Module verbex.verbex

+
+
+

Generate regular expressions from an easier fluent verbal form.

+
+ +Expand source code + +
"""Generate regular expressions from an easier fluent verbal form."""
+from __future__ import annotations
+
+import re
+from enum import Enum
+from functools import wraps
+
+try:
+    from typing import (  # <--------------- if Python ≥ 3.9.0
+        Annotated,
+        ParamSpec,
+        TypeAlias,
+    )
+except (ModuleNotFoundError, ImportError):
+    from typing_extensions import TypeAlias, Annotated, ParamSpec  # type: ignore # <--- if Python < 3.9.0
+
+from typing import Pattern, Protocol, TypeVar
+
+from beartype import beartype  # type: ignore
+from beartype.typing import (  # type: ignore
+    Any,
+    Callable,
+    Dict,
+    Iterator,
+    List,
+    Optional,
+    Tuple,
+    Union,
+    cast,
+    runtime_checkable,
+)
+from beartype.vale import Is  # type: ignore
+
+
+def _string_len_is_1(text: object) -> bool:
+    return isinstance(text, str) and len(text) == 1
+
+
+Char = Annotated[str, Is[_string_len_is_1]]
+
+
+P = ParamSpec("P")  # noqa VNE001
+R = TypeVar("R")  # noqa VNE001
+
+
+# work around for bug https://github.com/python/mypy/issues/12660
+# fixed in next version of mypy
+@runtime_checkable
+class HasIter(Protocol):
+    """Workaround for mypy P.args."""
+
+    def __iter__(self) -> Iterator[Any]:
+        """Object can be iterated.
+
+        Yields:
+            Next object.
+        """
+        ...
+
+
+# work around for bug https://github.com/python/mypy/issues/12660
+# fixed in next version of mypy
+@runtime_checkable
+class HasItems(Protocol):
+    """Workaround for mypy P.kwargs."""
+
+    def items(self) -> Tuple[str, Any]:
+        """Object has items method.
+
+        Returns:
+            The dict of items.
+        """
+        ...
+
+
+class EscapedText(str):
+    """Text that has been escaped for regex.
+
+    Arguments:
+        str -- Extend the string class.
+    """
+
+    def __new__(cls, value: str) -> EscapedText:
+        """Return a escaped regex string.
+
+        Arguments:
+            value -- the string to escape
+
+        Returns:
+            _description_
+        """
+        return str.__new__(cls, re.escape(value))
+
+
+def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+    """Automatically escape any string parameters as EscapedText.
+
+    Arguments:
+        func -- The function to decorate.
+
+    Returns:
+        The decorated function.
+    """
+
+    @wraps(func)
+    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+        escaped_args: List[Any] = []
+        escaped_kwargs: Dict[str, Any] = {}
+        for arg in cast(HasIter, args):
+            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+                escaped_args.append(EscapedText(arg))
+            else:
+                escaped_args.append(arg)
+        arg_k: str
+        arg_v: Any
+        for arg_k, arg_v in cast(HasItems, kwargs).items():
+            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+            else:
+                escaped_kwargs[arg_k] = arg_v
+        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+
+    return inner
+
+
+class CharClass(Enum):
+    """Enum of character classes in regex.
+
+    Arguments:
+        Enum -- Extends the Enum class.
+    """
+
+    DIGIT = "\\d"
+    LETTER = "\\w"
+    UPPERCASE_LETTER = "\\u"
+    LOWERCASE_LETTER = "\\l"
+    WHITESPACE = "\\s"
+    TAB = "\\t"
+
+    def __str__(self) -> str:
+        """To string method based on Enum value.
+
+        Returns:
+            value of Enum
+        """
+        return self.value
+
+
+class SpecialChar(Enum):
+    """Enum of special charaters, shorthand.
+
+    Arguments:
+        Enum -- Extends the Enum class.
+    """
+
+    # does not work  / should not be used in [ ]
+    LINEBREAK = "(\\n|(\\r\\n))"
+    START_OF_LINE = "^"
+    END_OF_LINE = "$"
+    TAB = "\t"
+
+    def __str__(self) -> str:
+        """To string for special chars enum.
+
+        Returns:
+            Return value of enum as string.
+        """
+        return self.value
+
+
+CharClassOrChars: TypeAlias = Union[str, CharClass]
+EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar]
+VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial]
+
+
+class Verbex:
+    """
+    VerbalExpressions class.
+
+    the following methods do not try to match the original js lib!
+    """
+
+    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+
+    @re_escape
+    @beartype
+    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+        """Create a Verbex object; setting any needed flags.
+
+        Keyword Arguments:
+            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+        """
+        # self._parts: List[str] = [text]
+        self._parts: List[str] = []
+        self._modifiers = modifiers
+
+    @property
+    def modifiers(self) -> re.RegexFlag:
+        """Return the modifiers for this Verbex object.
+
+        Returns:
+            The modifiers applied to this object.
+        """
+        return self._modifiers
+
+    def __str__(self) -> str:
+        """Return regex string representation."""
+        return "".join(self._parts)
+
+    @beartype
+    def _add(self, value: Union[str, List[str]]) -> Verbex:
+        """
+        Append a transformed value to internal expression to be compiled.
+
+        As possible, this method should be "private".
+        """
+        if isinstance(value, list):
+            self._parts.extend(value)
+        else:
+            self._parts.append(value)
+        return self
+
+    def regex(self) -> Pattern[str]:
+        """Get a regular expression object."""
+        return re.compile(
+            str(self),
+            self._modifiers,
+        )
+
+    # allow VerbexEscapedCharClassOrSpecial
+
+    @re_escape
+    @beartype
+    def _capture_group_with_name(
+        self,
+        name: str,
+        text: VerbexEscapedCharClassOrSpecial,
+    ) -> Verbex:
+        return self._add(f"(?<{name}>{str(text)})")
+
+    @re_escape
+    @beartype
+    def _capture_group_without_name(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+    ) -> Verbex:
+        return self._add(f"({str(text)})")
+
+    @re_escape
+    @beartype
+    def capture_group(
+        self,
+        /,
+        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+    ) -> Verbex:
+        """Create a capture group.
+
+        Name is optional if not specified then the first argument is the text.
+
+        Keyword Arguments:
+            name_or_text -- The name of the group / text to search for (default: {None})
+            text -- The text to search for (default: {None})
+
+        Raises:
+            ValueError: If name is specified then text must be as well.
+
+        Returns:
+            Verbex with added capture group.
+        """
+        if name_or_text is not None:
+            if text is None:
+                _text = name_or_text
+                return self._capture_group_without_name(_text)
+            if isinstance(name_or_text, str):
+                return self._capture_group_with_name(name_or_text, text)
+        raise ValueError("text must be specified with optional name")
+
+    @re_escape
+    @beartype
+    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+        """`or` is a python keyword so we use `OR` instead.
+
+        Arguments:
+            text -- Text to find or a Verbex object.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("|").find(text)
+
+    @re_escape
+    @beartype
+    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object zero or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})*")
+
+    @re_escape
+    @beartype
+    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object one or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})+")
+
+    @re_escape
+    @beartype
+    def n_times(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object n or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n}}}")
+
+    @re_escape
+    @beartype
+    def n_times_or_more(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object at least n times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n},}}")
+
+    @re_escape
+    @beartype
+    def n_to_m_times(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+        m: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object between n and m times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+
+    @re_escape
+    @beartype
+    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Possibly find the text / Verbex object.
+
+        Arguments:
+            text -- The text / Verbex object to possibly find.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})?")
+
+    @re_escape
+    @beartype
+    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(str(text))
+
+    @re_escape
+    @beartype
+    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Synonym for find.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(text)
+
+    @re_escape
+    @beartype
+    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is followed by text.
+
+        Positive lookahead
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?={text})")
+
+    @re_escape
+    @beartype
+    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not followed by text.
+
+        Negative lookahead
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?!{text})")
+
+    @re_escape
+    @beartype
+    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not preceded by text.
+
+        Positive lookbehind
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?<={text})")
+
+    @re_escape
+    @beartype
+    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not preceded by text.
+
+        Negative Lookbehind
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?<!{text})")
+
+    # only allow CharclassOrChars
+
+    @re_escape
+    @beartype
+    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+        """Find anything in this group of chars or char class.
+
+        Arguments:
+            text -- The characters to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:[{chargroup}])")
+
+    @re_escape
+    @beartype
+    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+        """Find anything but this group of chars or char class.
+
+        Arguments:
+            text -- The characters to not look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:[^{text}])")
+
+    @re_escape
+    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+        """Find anything one or more times but this group of chars or char class.
+
+        Arguments:
+            text -- The characters to not look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"[^{chargroup}]+")
+
+    # no text input
+
+    def start_of_line(self) -> Verbex:
+        """Find the start of the line.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.START_OF_LINE)
+
+    def end_of_line(self) -> Verbex:
+        """Find the end of the line.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.END_OF_LINE)
+
+    def line_break(self) -> Verbex:
+        """Find a line break.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.LINEBREAK)
+
+    def tab(self) -> Verbex:
+        """Find a tab.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.TAB)
+
+    def anything(self) -> Verbex:
+        """Find anything one or more time.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(".+")
+
+    def as_few(self) -> Verbex:
+        """Modify previous search to not be greedy.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("?")
+
+    @beartype
+    def number_range(self, start: int, end: int) -> Verbex:
+        """Generate a range of numbers.
+
+        Arguments:
+            start -- Start of the range
+            end -- End of the range
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+
+    @beartype
+    def letter_range(self, start: Char, end: Char) -> Verbex:
+        """Generate a range of letters.
+
+        Arguments:
+            start -- Start of the range
+            end -- End of the range
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"[{start}-{end}]")
+
+    def word(self) -> Verbex:
+        """Find a word on word boundary.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("(\\b\\w+\\b)")
+
+    # # --------------- modifiers ------------------------
+
+    def with_any_case(self) -> Verbex:
+        """Modify Verbex object to be case insensitive.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.IGNORECASE
+        return self
+
+    def search_by_line(self) -> Verbex:
+        """Search each line, ^ and $ match begining and end of line respectively.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.MULTILINE
+        return self
+
+    def with_ascii(self) -> Verbex:
+        """Match ascii instead of unicode.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.ASCII
+        return self
+
+
+# left over notes from original version
+# def __getattr__(self, attr):
+#     """ any other function will be sent to the regex object """
+#     regex = self.regex()
+#     return getattr(regex, attr)
+
+# def replace(self, string, repl):
+#     return self.sub(repl, string)
+
+
+if __name__ == "__main__":
+    pass
+
+
+
+
+
+
+
+

Functions

+
+
+def re_escape(func: Callable[P, R]) ‑> collections.abc.Callable[~P, ~R] +
+
+

Automatically escape any string parameters as EscapedText.

+

Arguments

+

func – The function to decorate.

+

Returns

+

The decorated function.

+
+ +Expand source code + +
def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+    """Automatically escape any string parameters as EscapedText.
+
+    Arguments:
+        func -- The function to decorate.
+
+    Returns:
+        The decorated function.
+    """
+
+    @wraps(func)
+    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+        escaped_args: List[Any] = []
+        escaped_kwargs: Dict[str, Any] = {}
+        for arg in cast(HasIter, args):
+            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+                escaped_args.append(EscapedText(arg))
+            else:
+                escaped_args.append(arg)
+        arg_k: str
+        arg_v: Any
+        for arg_k, arg_v in cast(HasItems, kwargs).items():
+            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+            else:
+                escaped_kwargs[arg_k] = arg_v
+        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+
+    return inner
+
+
+
+
+
+

Classes

+
+
+class CharClass +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+
+

Enum of character classes in regex.

+

Arguments

+

Enum – Extends the Enum class.

+
+ +Expand source code + +
class CharClass(Enum):
+    """Enum of character classes in regex.
+
+    Arguments:
+        Enum -- Extends the Enum class.
+    """
+
+    DIGIT = "\\d"
+    LETTER = "\\w"
+    UPPERCASE_LETTER = "\\u"
+    LOWERCASE_LETTER = "\\l"
+    WHITESPACE = "\\s"
+    TAB = "\\t"
+
+    def __str__(self) -> str:
+        """To string method based on Enum value.
+
+        Returns:
+            value of Enum
+        """
+        return self.value
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var DIGIT
+
+
+
+
var LETTER
+
+
+
+
var LOWERCASE_LETTER
+
+
+
+
var TAB
+
+
+
+
var UPPERCASE_LETTER
+
+
+
+
var WHITESPACE
+
+
+
+
+
+
+class EscapedText +(value: str) +
+
+

Text that has been escaped for regex.

+

Arguments

+

str – Extend the string class.

+
+ +Expand source code + +
class EscapedText(str):
+    """Text that has been escaped for regex.
+
+    Arguments:
+        str -- Extend the string class.
+    """
+
+    def __new__(cls, value: str) -> EscapedText:
+        """Return a escaped regex string.
+
+        Arguments:
+            value -- the string to escape
+
+        Returns:
+            _description_
+        """
+        return str.__new__(cls, re.escape(value))
+
+

Ancestors

+
    +
  • builtins.str
  • +
+
+
+class HasItems +(*args, **kwargs) +
+
+

Workaround for mypy P.kwargs.

+
+ +Expand source code + +
@runtime_checkable
+class HasItems(Protocol):
+    """Workaround for mypy P.kwargs."""
+
+    def items(self) -> Tuple[str, Any]:
+        """Object has items method.
+
+        Returns:
+            The dict of items.
+        """
+        ...
+
+

Ancestors

+
    +
  • typing.Protocol
  • +
  • typing.Generic
  • +
+

Methods

+
+
+def items(self) ‑> tuple[str, typing.Any] +
+
+

Object has items method.

+

Returns

+

The dict of items.

+
+ +Expand source code + +
def items(self) -> Tuple[str, Any]:
+    """Object has items method.
+
+    Returns:
+        The dict of items.
+    """
+    ...
+
+
+
+
+
+class HasIter +(*args, **kwargs) +
+
+

Workaround for mypy P.args.

+
+ +Expand source code + +
@runtime_checkable
+class HasIter(Protocol):
+    """Workaround for mypy P.args."""
+
+    def __iter__(self) -> Iterator[Any]:
+        """Object can be iterated.
+
+        Yields:
+            Next object.
+        """
+        ...
+
+

Ancestors

+
    +
  • typing.Protocol
  • +
  • typing.Generic
  • +
+
+
+class SpecialChar +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+
+

Enum of special charaters, shorthand.

+

Arguments

+

Enum – Extends the Enum class.

+
+ +Expand source code + +
class SpecialChar(Enum):
+    """Enum of special charaters, shorthand.
+
+    Arguments:
+        Enum -- Extends the Enum class.
+    """
+
+    # does not work  / should not be used in [ ]
+    LINEBREAK = "(\\n|(\\r\\n))"
+    START_OF_LINE = "^"
+    END_OF_LINE = "$"
+    TAB = "\t"
+
+    def __str__(self) -> str:
+        """To string for special chars enum.
+
+        Returns:
+            Return value of enum as string.
+        """
+        return self.value
+
+

Ancestors

+
    +
  • enum.Enum
  • +
+

Class variables

+
+
var END_OF_LINE
+
+
+
+
var LINEBREAK
+
+
+
+
var START_OF_LINE
+
+
+
+
var TAB
+
+
+
+
+
+
+class Verbex +(modifiers: re.RegexFlag = ) +
+
+

VerbalExpressions class.

+

the following methods do not try to match the original js lib!

+

Create a Verbex object; setting any needed flags.

+

Keyword Arguments: +modifiers – Regex modifying flags (default: {re.RegexFlag(0)})

+
+ +Expand source code + +
class Verbex:
+    """
+    VerbalExpressions class.
+
+    the following methods do not try to match the original js lib!
+    """
+
+    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+
+    @re_escape
+    @beartype
+    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+        """Create a Verbex object; setting any needed flags.
+
+        Keyword Arguments:
+            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+        """
+        # self._parts: List[str] = [text]
+        self._parts: List[str] = []
+        self._modifiers = modifiers
+
+    @property
+    def modifiers(self) -> re.RegexFlag:
+        """Return the modifiers for this Verbex object.
+
+        Returns:
+            The modifiers applied to this object.
+        """
+        return self._modifiers
+
+    def __str__(self) -> str:
+        """Return regex string representation."""
+        return "".join(self._parts)
+
+    @beartype
+    def _add(self, value: Union[str, List[str]]) -> Verbex:
+        """
+        Append a transformed value to internal expression to be compiled.
+
+        As possible, this method should be "private".
+        """
+        if isinstance(value, list):
+            self._parts.extend(value)
+        else:
+            self._parts.append(value)
+        return self
+
+    def regex(self) -> Pattern[str]:
+        """Get a regular expression object."""
+        return re.compile(
+            str(self),
+            self._modifiers,
+        )
+
+    # allow VerbexEscapedCharClassOrSpecial
+
+    @re_escape
+    @beartype
+    def _capture_group_with_name(
+        self,
+        name: str,
+        text: VerbexEscapedCharClassOrSpecial,
+    ) -> Verbex:
+        return self._add(f"(?<{name}>{str(text)})")
+
+    @re_escape
+    @beartype
+    def _capture_group_without_name(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+    ) -> Verbex:
+        return self._add(f"({str(text)})")
+
+    @re_escape
+    @beartype
+    def capture_group(
+        self,
+        /,
+        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+    ) -> Verbex:
+        """Create a capture group.
+
+        Name is optional if not specified then the first argument is the text.
+
+        Keyword Arguments:
+            name_or_text -- The name of the group / text to search for (default: {None})
+            text -- The text to search for (default: {None})
+
+        Raises:
+            ValueError: If name is specified then text must be as well.
+
+        Returns:
+            Verbex with added capture group.
+        """
+        if name_or_text is not None:
+            if text is None:
+                _text = name_or_text
+                return self._capture_group_without_name(_text)
+            if isinstance(name_or_text, str):
+                return self._capture_group_with_name(name_or_text, text)
+        raise ValueError("text must be specified with optional name")
+
+    @re_escape
+    @beartype
+    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+        """`or` is a python keyword so we use `OR` instead.
+
+        Arguments:
+            text -- Text to find or a Verbex object.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("|").find(text)
+
+    @re_escape
+    @beartype
+    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object zero or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})*")
+
+    @re_escape
+    @beartype
+    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object one or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})+")
+
+    @re_escape
+    @beartype
+    def n_times(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object n or more times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n}}}")
+
+    @re_escape
+    @beartype
+    def n_times_or_more(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object at least n times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n},}}")
+
+    @re_escape
+    @beartype
+    def n_to_m_times(
+        self,
+        text: VerbexEscapedCharClassOrSpecial,
+        n: int,  # noqa: VNE001
+        m: int,  # noqa: VNE001
+    ) -> Verbex:
+        """Find the text or Verbex object between n and m times.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+
+    @re_escape
+    @beartype
+    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Possibly find the text / Verbex object.
+
+        Arguments:
+            text -- The text / Verbex object to possibly find.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:{str(text)})?")
+
+    @re_escape
+    @beartype
+    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Find the text or Verbex object.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(str(text))
+
+    @re_escape
+    @beartype
+    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Synonym for find.
+
+        Arguments:
+            text -- The text / Verbex object to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(text)
+
+    @re_escape
+    @beartype
+    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is followed by text.
+
+        Positive lookahead
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?={text})")
+
+    @re_escape
+    @beartype
+    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not followed by text.
+
+        Negative lookahead
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?!{text})")
+
+    @re_escape
+    @beartype
+    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not preceded by text.
+
+        Positive lookbehind
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?<={text})")
+
+    @re_escape
+    @beartype
+    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+        """Match if string is not preceded by text.
+
+        Negative Lookbehind
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?<!{text})")
+
+    # only allow CharclassOrChars
+
+    @re_escape
+    @beartype
+    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+        """Find anything in this group of chars or char class.
+
+        Arguments:
+            text -- The characters to look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:[{chargroup}])")
+
+    @re_escape
+    @beartype
+    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+        """Find anything but this group of chars or char class.
+
+        Arguments:
+            text -- The characters to not look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"(?:[^{text}])")
+
+    @re_escape
+    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+        """Find anything one or more times but this group of chars or char class.
+
+        Arguments:
+            text -- The characters to not look for.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"[^{chargroup}]+")
+
+    # no text input
+
+    def start_of_line(self) -> Verbex:
+        """Find the start of the line.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.START_OF_LINE)
+
+    def end_of_line(self) -> Verbex:
+        """Find the end of the line.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.END_OF_LINE)
+
+    def line_break(self) -> Verbex:
+        """Find a line break.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.LINEBREAK)
+
+    def tab(self) -> Verbex:
+        """Find a tab.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self.find(SpecialChar.TAB)
+
+    def anything(self) -> Verbex:
+        """Find anything one or more time.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(".+")
+
+    def as_few(self) -> Verbex:
+        """Modify previous search to not be greedy.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("?")
+
+    @beartype
+    def number_range(self, start: int, end: int) -> Verbex:
+        """Generate a range of numbers.
+
+        Arguments:
+            start -- Start of the range
+            end -- End of the range
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+
+    @beartype
+    def letter_range(self, start: Char, end: Char) -> Verbex:
+        """Generate a range of letters.
+
+        Arguments:
+            start -- Start of the range
+            end -- End of the range
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add(f"[{start}-{end}]")
+
+    def word(self) -> Verbex:
+        """Find a word on word boundary.
+
+        Returns:
+            Modified Verbex object.
+        """
+        return self._add("(\\b\\w+\\b)")
+
+    # # --------------- modifiers ------------------------
+
+    def with_any_case(self) -> Verbex:
+        """Modify Verbex object to be case insensitive.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.IGNORECASE
+        return self
+
+    def search_by_line(self) -> Verbex:
+        """Search each line, ^ and $ match begining and end of line respectively.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.MULTILINE
+        return self
+
+    def with_ascii(self) -> Verbex:
+        """Match ascii instead of unicode.
+
+        Returns:
+            Modified Verbex object.
+        """
+        self._modifiers |= re.ASCII
+        return self
+
+

Class variables

+
+
var EMPTY_REGEX_FLAG
+
+
+
+
+

Instance variables

+
+
var modifiers : re.RegexFlag
+
+

Return the modifiers for this Verbex object.

+

Returns

+

The modifiers applied to this object.

+
+ +Expand source code + +
@property
+def modifiers(self) -> re.RegexFlag:
+    """Return the modifiers for this Verbex object.
+
+    Returns:
+        The modifiers applied to this object.
+    """
+    return self._modifiers
+
+
+
+

Methods

+
+
+def OR(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

or is a python keyword so we use OR instead.

+

Arguments

+

text – Text to find or a Verbex object.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+    """`or` is a python keyword so we use `OR` instead.
+
+    Arguments:
+        text -- Text to find or a Verbex object.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add("|").find(text)
+
+
+
+def any_of(self, chargroup: Union[str, CharClass]) ‑> Verbex +
+
+

Find anything in this group of chars or char class.

+

Arguments

+

text – The characters to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+    """Find anything in this group of chars or char class.
+
+    Arguments:
+        text -- The characters to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:[{chargroup}])")
+
+
+
+def anything(self) ‑> Verbex +
+
+

Find anything one or more time.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def anything(self) -> Verbex:
+    """Find anything one or more time.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(".+")
+
+
+
+def anything_but(self, chargroup: EscapedCharClassOrSpecial) ‑> Verbex +
+
+

Find anything one or more times but this group of chars or char class.

+

Arguments

+

text – The characters to not look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+    """Find anything one or more times but this group of chars or char class.
+
+    Arguments:
+        text -- The characters to not look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"[^{chargroup}]+")
+
+
+
+def as_few(self) ‑> Verbex +
+
+

Modify previous search to not be greedy.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def as_few(self) -> Verbex:
+    """Modify previous search to not be greedy.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add("?")
+
+
+
+def capture_group(self, /, name_or_text: Union[str, ForwardRef(None), ForwardRef('Verbex'), CharClassSpecialChar] = None, text: Union[str, ForwardRef(None), ForwardRef('Verbex'), CharClassSpecialChar] = None) ‑> Verbex +
+
+

Create a capture group.

+

Name is optional if not specified then the first argument is the text.

+

Keyword Arguments: +name_or_text – The name of the group / text to search for (default: {None}) +text – The text to search for (default: {None})

+

Raises

+
+
ValueError
+
If name is specified then text must be as well.
+
+

Returns

+

Verbex with added capture group.

+
+ +Expand source code + +
@re_escape
+@beartype
+def capture_group(
+    self,
+    /,
+    name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+    text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+) -> Verbex:
+    """Create a capture group.
+
+    Name is optional if not specified then the first argument is the text.
+
+    Keyword Arguments:
+        name_or_text -- The name of the group / text to search for (default: {None})
+        text -- The text to search for (default: {None})
+
+    Raises:
+        ValueError: If name is specified then text must be as well.
+
+    Returns:
+        Verbex with added capture group.
+    """
+    if name_or_text is not None:
+        if text is None:
+            _text = name_or_text
+            return self._capture_group_without_name(_text)
+        if isinstance(name_or_text, str):
+            return self._capture_group_with_name(name_or_text, text)
+    raise ValueError("text must be specified with optional name")
+
+
+
+def end_of_line(self) ‑> Verbex +
+
+

Find the end of the line.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def end_of_line(self) -> Verbex:
+    """Find the end of the line.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self.find(SpecialChar.END_OF_LINE)
+
+
+
+def find(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Find the text or Verbex object.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Find the text or Verbex object.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(str(text))
+
+
+
+def followed_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Match if string is followed by text.

+

Positive lookahead

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Match if string is followed by text.
+
+    Positive lookahead
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?={text})")
+
+
+
+def letter_range(self, start: typing.Annotated[str, Is[_string_len_is_1]], end: typing.Annotated[str, Is[_string_len_is_1]]) ‑> Verbex +
+
+

Generate a range of letters.

+

Arguments

+

start – Start of the range +end – End of the range

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@beartype
+def letter_range(self, start: Char, end: Char) -> Verbex:
+    """Generate a range of letters.
+
+    Arguments:
+        start -- Start of the range
+        end -- End of the range
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"[{start}-{end}]")
+
+
+
+def line_break(self) ‑> Verbex +
+
+

Find a line break.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def line_break(self) -> Verbex:
+    """Find a line break.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self.find(SpecialChar.LINEBREAK)
+
+
+
+def maybe(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Possibly find the text / Verbex object.

+

Arguments

+

text – The text / Verbex object to possibly find.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Possibly find the text / Verbex object.
+
+    Arguments:
+        text -- The text / Verbex object to possibly find.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)})?")
+
+
+
+def n_times(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int) ‑> Verbex +
+
+

Find the text or Verbex object n or more times.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def n_times(
+    self,
+    text: VerbexEscapedCharClassOrSpecial,
+    n: int,  # noqa: VNE001
+) -> Verbex:
+    """Find the text or Verbex object n or more times.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)}){{{n}}}")
+
+
+
+def n_times_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int) ‑> Verbex +
+
+

Find the text or Verbex object at least n times.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def n_times_or_more(
+    self,
+    text: VerbexEscapedCharClassOrSpecial,
+    n: int,  # noqa: VNE001
+) -> Verbex:
+    """Find the text or Verbex object at least n times.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)}){{{n},}}")
+
+
+
+def n_to_m_times(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int, m: int) ‑> Verbex +
+
+

Find the text or Verbex object between n and m times.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def n_to_m_times(
+    self,
+    text: VerbexEscapedCharClassOrSpecial,
+    n: int,  # noqa: VNE001
+    m: int,  # noqa: VNE001
+) -> Verbex:
+    """Find the text or Verbex object between n and m times.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)}){{{n},{m}}}")
+
+
+
+def not_any_of(self, text: Union[str, CharClass]) ‑> Verbex +
+
+

Find anything but this group of chars or char class.

+

Arguments

+

text – The characters to not look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def not_any_of(self, text: CharClassOrChars) -> Verbex:
+    """Find anything but this group of chars or char class.
+
+    Arguments:
+        text -- The characters to not look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:[^{text}])")
+
+
+
+def not_followed_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Match if string is not followed by text.

+

Negative lookahead

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Match if string is not followed by text.
+
+    Negative lookahead
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?!{text})")
+
+
+
+def not_preceded_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Match if string is not preceded by text.

+

Negative Lookbehind

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Match if string is not preceded by text.
+
+    Negative Lookbehind
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?<!{text})")
+
+
+
+def number_range(self, start: int, end: int) ‑> Verbex +
+
+

Generate a range of numbers.

+

Arguments

+

start – Start of the range +end – End of the range

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@beartype
+def number_range(self, start: int, end: int) -> Verbex:
+    """Generate a range of numbers.
+
+    Arguments:
+        start -- Start of the range
+        end -- End of the range
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+
+
+
+def one_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Find the text or Verbex object one or more times.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Find the text or Verbex object one or more times.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)})+")
+
+
+
+def preceded_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Match if string is not preceded by text.

+

Positive lookbehind

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Match if string is not preceded by text.
+
+    Positive lookbehind
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?<={text})")
+
+
+
+def regex(self) ‑> Pattern[str] +
+
+

Get a regular expression object.

+
+ +Expand source code + +
def regex(self) -> Pattern[str]:
+    """Get a regular expression object."""
+    return re.compile(
+        str(self),
+        self._modifiers,
+    )
+
+
+
+def search_by_line(self) ‑> Verbex +
+
+

Search each line, ^ and $ match begining and end of line respectively.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def search_by_line(self) -> Verbex:
+    """Search each line, ^ and $ match begining and end of line respectively.
+
+    Returns:
+        Modified Verbex object.
+    """
+    self._modifiers |= re.MULTILINE
+    return self
+
+
+
+def start_of_line(self) ‑> Verbex +
+
+

Find the start of the line.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def start_of_line(self) -> Verbex:
+    """Find the start of the line.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self.find(SpecialChar.START_OF_LINE)
+
+
+
+def tab(self) ‑> Verbex +
+
+

Find a tab.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def tab(self) -> Verbex:
+    """Find a tab.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self.find(SpecialChar.TAB)
+
+
+
+def then(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Synonym for find.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Synonym for find.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self.find(text)
+
+
+
+def with_any_case(self) ‑> Verbex +
+
+

Modify Verbex object to be case insensitive.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def with_any_case(self) -> Verbex:
+    """Modify Verbex object to be case insensitive.
+
+    Returns:
+        Modified Verbex object.
+    """
+    self._modifiers |= re.IGNORECASE
+    return self
+
+
+
+def with_ascii(self) ‑> Verbex +
+
+

Match ascii instead of unicode.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def with_ascii(self) -> Verbex:
+    """Match ascii instead of unicode.
+
+    Returns:
+        Modified Verbex object.
+    """
+    self._modifiers |= re.ASCII
+    return self
+
+
+
+def word(self) ‑> Verbex +
+
+

Find a word on word boundary.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
def word(self) -> Verbex:
+    """Find a word on word boundary.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add("(\\b\\w+\\b)")
+
+
+
+def zero_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex +
+
+

Find the text or Verbex object zero or more times.

+

Arguments

+

text – The text / Verbex object to look for.

+

Returns

+

Modified Verbex object.

+
+ +Expand source code + +
@re_escape
+@beartype
+def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+    """Find the text or Verbex object zero or more times.
+
+    Arguments:
+        text -- The text / Verbex object to look for.
+
+    Returns:
+        Modified Verbex object.
+    """
+    return self._add(f"(?:{str(text)})*")
+
+
+
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/setup.py b/setup.py index a2f1f6a..34d1196 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name="Verbex", - version="1.0.2", + version="1.0.3", description=( "Make difficult regular expressions easy! Python fork based on of the awesome" " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" @@ -19,7 +19,7 @@ " Raghuram, Kharms, Richard Broderick" ), license="GPLv3", - url="https://github.com/VerbalExpressions/PythonVerbalExpressions", + url="https://github.com/rbroderi/Verbex", test_suite="tests", packages=["verbex"], classifiers=[ From b15001486bff1055790ed3ceff7f8e9a2a15add6 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 23:45:42 -0400 Subject: [PATCH 27/90] switch to pdoc from pdoc3 --- builddoc.bat | 3 + html/index.html | 7 + html/search.js | 46 + html/verbex/index.html | 68 - html/verbex/verbex.html | 5317 +++++++++++++++++++++++---------------- 5 files changed, 3139 insertions(+), 2302 deletions(-) create mode 100644 builddoc.bat create mode 100644 html/index.html create mode 100644 html/search.js delete mode 100644 html/verbex/index.html diff --git a/builddoc.bat b/builddoc.bat new file mode 100644 index 0000000..e2920aa --- /dev/null +++ b/builddoc.bat @@ -0,0 +1,3 @@ +pushd "%~dp0" +pdoc verbex/verbex -o html +pause \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..0e34fd0 --- /dev/null +++ b/html/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/html/search.js b/html/search.js new file mode 100644 index 0000000..c09a00a --- /dev/null +++ b/html/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oGenerate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n /,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/html/verbex/index.html b/html/verbex/index.html deleted file mode 100644 index fa7fe2b..0000000 --- a/html/verbex/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -verbex API documentation - - - - - - - - - - - -
-
-
-

Package verbex

-
-
-
- -Expand source code - -
from .verbex import CharClass as CharClass
-from .verbex import SpecialChar as SpecialChar
-from .verbex import Verbex as Verbex
-
-
-
-

Sub-modules

-
-
verbex.verbex
-
-

Generate regular expressions from an easier fluent verbal form.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/html/verbex/verbex.html b/html/verbex/verbex.html index 639b601..7d497c4 100644 --- a/html/verbex/verbex.html +++ b/html/verbex/verbex.html @@ -1,2290 +1,3139 @@ - - - -verbex.verbex API documentation - - - - - - - - - - + + + + verbex.verbex API documentation + + + + + + + -
-
-
-

Module verbex.verbex

-
-
-

Generate regular expressions from an easier fluent verbal form.

-
- -Expand source code - -
"""Generate regular expressions from an easier fluent verbal form."""
-from __future__ import annotations
+    
+    
+
+

+verbex.verbex

-import re -from enum import Enum -from functools import wraps - -try: - from typing import ( # <--------------- if Python ≥ 3.9.0 - Annotated, - ParamSpec, - TypeAlias, - ) -except (ModuleNotFoundError, ImportError): - from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 - -from typing import Pattern, Protocol, TypeVar - -from beartype import beartype # type: ignore -from beartype.typing import ( # type: ignore - Any, - Callable, - Dict, - Iterator, - List, - Optional, - Tuple, - Union, - cast, - runtime_checkable, -) -from beartype.vale import Is # type: ignore - - -def _string_len_is_1(text: object) -> bool: - return isinstance(text, str) and len(text) == 1 - - -Char = Annotated[str, Is[_string_len_is_1]] - - -P = ParamSpec("P") # noqa VNE001 -R = TypeVar("R") # noqa VNE001 - - -# work around for bug https://github.com/python/mypy/issues/12660 -# fixed in next version of mypy -@runtime_checkable -class HasIter(Protocol): - """Workaround for mypy P.args.""" - - def __iter__(self) -> Iterator[Any]: - """Object can be iterated. - - Yields: - Next object. - """ - ... - - -# work around for bug https://github.com/python/mypy/issues/12660 -# fixed in next version of mypy -@runtime_checkable -class HasItems(Protocol): - """Workaround for mypy P.kwargs.""" - - def items(self) -> Tuple[str, Any]: - """Object has items method. - - Returns: - The dict of items. - """ - ... +

Generate regular expressions from an easier fluent verbal form.

+
+
+ View Source +
  0"""Generate regular expressions from an easier fluent verbal form."""
+  1from __future__ import annotations
+  2
+  3import re
+  4from enum import Enum
+  5from functools import wraps
+  6
+  7try:
+  8    from typing import (  # <--------------- if Python ≥ 3.9.0
+  9        Annotated,
+ 10        ParamSpec,
+ 11        TypeAlias,
+ 12    )
+ 13except (ModuleNotFoundError, ImportError):
+ 14    from typing_extensions import TypeAlias, Annotated, ParamSpec  # type: ignore # <--- if Python < 3.9.0
+ 15
+ 16from typing import Pattern, Protocol, TypeVar
+ 17
+ 18from beartype import beartype  # type: ignore
+ 19from beartype.typing import (  # type: ignore
+ 20    Any,
+ 21    Callable,
+ 22    Dict,
+ 23    Iterator,
+ 24    List,
+ 25    Optional,
+ 26    Tuple,
+ 27    Union,
+ 28    cast,
+ 29    runtime_checkable,
+ 30)
+ 31from beartype.vale import Is  # type: ignore
+ 32
+ 33
+ 34def _string_len_is_1(text: object) -> bool:
+ 35    return isinstance(text, str) and len(text) == 1
+ 36
+ 37
+ 38Char = Annotated[str, Is[_string_len_is_1]]
+ 39
+ 40
+ 41P = ParamSpec("P")  # noqa VNE001
+ 42R = TypeVar("R")  # noqa VNE001
+ 43
+ 44
+ 45# work around for bug https://github.com/python/mypy/issues/12660
+ 46# fixed in next version of mypy
+ 47@runtime_checkable
+ 48class HasIter(Protocol):
+ 49    """Workaround for mypy P.args."""
+ 50
+ 51    def __iter__(self) -> Iterator[Any]:
+ 52        """Object can be iterated.
+ 53
+ 54        Yields:
+ 55            Next object.
+ 56        """
+ 57        ...
+ 58
+ 59
+ 60# work around for bug https://github.com/python/mypy/issues/12660
+ 61# fixed in next version of mypy
+ 62@runtime_checkable
+ 63class HasItems(Protocol):
+ 64    """Workaround for mypy P.kwargs."""
+ 65
+ 66    def items(self) -> Tuple[str, Any]:
+ 67        """Object has items method.
+ 68
+ 69        Returns:
+ 70            The dict of items.
+ 71        """
+ 72        ...
+ 73
+ 74
+ 75class EscapedText(str):
+ 76    """Text that has been escaped for regex.
+ 77
+ 78    Arguments:
+ 79        str -- Extend the string class.
+ 80    """
+ 81
+ 82    def __new__(cls, value: str) -> EscapedText:
+ 83        """Return a escaped regex string.
+ 84
+ 85        Arguments:
+ 86            value -- the string to escape
+ 87
+ 88        Returns:
+ 89            _description_
+ 90        """
+ 91        return str.__new__(cls, re.escape(value))
+ 92
+ 93
+ 94def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+ 95    """Automatically escape any string parameters as EscapedText.
+ 96
+ 97    Arguments:
+ 98        func -- The function to decorate.
+ 99
+100    Returns:
+101        The decorated function.
+102    """
+103
+104    @wraps(func)
+105    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+106        escaped_args: List[Any] = []
+107        escaped_kwargs: Dict[str, Any] = {}
+108        for arg in cast(HasIter, args):
+109            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+110                escaped_args.append(EscapedText(arg))
+111            else:
+112                escaped_args.append(arg)
+113        arg_k: str
+114        arg_v: Any
+115        for arg_k, arg_v in cast(HasItems, kwargs).items():
+116            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+117                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+118            else:
+119                escaped_kwargs[arg_k] = arg_v
+120        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+121
+122    return inner
+123
+124
+125class CharClass(Enum):
+126    """Enum of character classes in regex.
+127
+128    Arguments:
+129        Enum -- Extends the Enum class.
+130    """
+131
+132    DIGIT = "\\d"
+133    LETTER = "\\w"
+134    UPPERCASE_LETTER = "\\u"
+135    LOWERCASE_LETTER = "\\l"
+136    WHITESPACE = "\\s"
+137    TAB = "\\t"
+138
+139    def __str__(self) -> str:
+140        """To string method based on Enum value.
+141
+142        Returns:
+143            value of Enum
+144        """
+145        return self.value
+146
+147
+148class SpecialChar(Enum):
+149    """Enum of special charaters, shorthand.
+150
+151    Arguments:
+152        Enum -- Extends the Enum class.
+153    """
+154
+155    # does not work  / should not be used in [ ]
+156    LINEBREAK = "(\\n|(\\r\\n))"
+157    START_OF_LINE = "^"
+158    END_OF_LINE = "$"
+159    TAB = "\t"
+160
+161    def __str__(self) -> str:
+162        """To string for special chars enum.
+163
+164        Returns:
+165            Return value of enum as string.
+166        """
+167        return self.value
+168
+169
+170CharClassOrChars: TypeAlias = Union[str, CharClass]
+171EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar]
+172VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial]
+173
+174
+175class Verbex:
+176    """
+177    VerbalExpressions class.
+178
+179    the following methods do not try to match the original js lib!
+180    """
+181
+182    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+183
+184    @re_escape
+185    @beartype
+186    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+187        """Create a Verbex object; setting any needed flags.
+188
+189        Keyword Arguments:
+190            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+191        """
+192        # self._parts: List[str] = [text]
+193        self._parts: List[str] = []
+194        self._modifiers = modifiers
+195
+196    @property
+197    def modifiers(self) -> re.RegexFlag:
+198        """Return the modifiers for this Verbex object.
+199
+200        Returns:
+201            The modifiers applied to this object.
+202        """
+203        return self._modifiers
+204
+205    def __str__(self) -> str:
+206        """Return regex string representation."""
+207        return "".join(self._parts)
+208
+209    @beartype
+210    def _add(self, value: Union[str, List[str]]) -> Verbex:
+211        """
+212        Append a transformed value to internal expression to be compiled.
+213
+214        As possible, this method should be "private".
+215        """
+216        if isinstance(value, list):
+217            self._parts.extend(value)
+218        else:
+219            self._parts.append(value)
+220        return self
+221
+222    def regex(self) -> Pattern[str]:
+223        """Get a regular expression object."""
+224        return re.compile(
+225            str(self),
+226            self._modifiers,
+227        )
+228
+229    # allow VerbexEscapedCharClassOrSpecial
+230
+231    @re_escape
+232    @beartype
+233    def _capture_group_with_name(
+234        self,
+235        name: str,
+236        text: VerbexEscapedCharClassOrSpecial,
+237    ) -> Verbex:
+238        return self._add(f"(?<{name}>{str(text)})")
+239
+240    @re_escape
+241    @beartype
+242    def _capture_group_without_name(
+243        self,
+244        text: VerbexEscapedCharClassOrSpecial,
+245    ) -> Verbex:
+246        return self._add(f"({str(text)})")
+247
+248    @re_escape
+249    @beartype
+250    def capture_group(
+251        self,
+252        /,
+253        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+254        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+255    ) -> Verbex:
+256        """Create a capture group.
+257
+258        Name is optional if not specified then the first argument is the text.
+259
+260        Keyword Arguments:
+261            name_or_text -- The name of the group / text to search for (default: {None})
+262            text -- The text to search for (default: {None})
+263
+264        Raises:
+265            ValueError: If name is specified then text must be as well.
+266
+267        Returns:
+268            Verbex with added capture group.
+269        """
+270        if name_or_text is not None:
+271            if text is None:
+272                _text = name_or_text
+273                return self._capture_group_without_name(_text)
+274            if isinstance(name_or_text, str):
+275                return self._capture_group_with_name(name_or_text, text)
+276        raise ValueError("text must be specified with optional name")
+277
+278    @re_escape
+279    @beartype
+280    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+281        """`or` is a python keyword so we use `OR` instead.
+282
+283        Arguments:
+284            text -- Text to find or a Verbex object.
+285
+286        Returns:
+287            Modified Verbex object.
+288        """
+289        return self._add("|").find(text)
+290
+291    @re_escape
+292    @beartype
+293    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+294        """Find the text or Verbex object zero or more times.
+295
+296        Arguments:
+297            text -- The text / Verbex object to look for.
+298
+299        Returns:
+300            Modified Verbex object.
+301        """
+302        return self._add(f"(?:{str(text)})*")
+303
+304    @re_escape
+305    @beartype
+306    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+307        """Find the text or Verbex object one or more times.
+308
+309        Arguments:
+310            text -- The text / Verbex object to look for.
+311
+312        Returns:
+313            Modified Verbex object.
+314        """
+315        return self._add(f"(?:{str(text)})+")
+316
+317    @re_escape
+318    @beartype
+319    def n_times(
+320        self,
+321        text: VerbexEscapedCharClassOrSpecial,
+322        n: int,  # noqa: VNE001
+323    ) -> Verbex:
+324        """Find the text or Verbex object n or more times.
+325
+326        Arguments:
+327            text -- The text / Verbex object to look for.
+328
+329        Returns:
+330            Modified Verbex object.
+331        """
+332        return self._add(f"(?:{str(text)}){{{n}}}")
+333
+334    @re_escape
+335    @beartype
+336    def n_times_or_more(
+337        self,
+338        text: VerbexEscapedCharClassOrSpecial,
+339        n: int,  # noqa: VNE001
+340    ) -> Verbex:
+341        """Find the text or Verbex object at least n times.
+342
+343        Arguments:
+344            text -- The text / Verbex object to look for.
+345
+346        Returns:
+347            Modified Verbex object.
+348        """
+349        return self._add(f"(?:{str(text)}){{{n},}}")
+350
+351    @re_escape
+352    @beartype
+353    def n_to_m_times(
+354        self,
+355        text: VerbexEscapedCharClassOrSpecial,
+356        n: int,  # noqa: VNE001
+357        m: int,  # noqa: VNE001
+358    ) -> Verbex:
+359        """Find the text or Verbex object between n and m times.
+360
+361        Arguments:
+362            text -- The text / Verbex object to look for.
+363
+364        Returns:
+365            Modified Verbex object.
+366        """
+367        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+368
+369    @re_escape
+370    @beartype
+371    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+372        """Possibly find the text / Verbex object.
+373
+374        Arguments:
+375            text -- The text / Verbex object to possibly find.
+376
+377        Returns:
+378            Modified Verbex object.
+379        """
+380        return self._add(f"(?:{str(text)})?")
+381
+382    @re_escape
+383    @beartype
+384    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+385        """Find the text or Verbex object.
+386
+387        Arguments:
+388            text -- The text / Verbex object to look for.
+389
+390        Returns:
+391            Modified Verbex object.
+392        """
+393        return self._add(str(text))
+394
+395    @re_escape
+396    @beartype
+397    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+398        """Synonym for find.
+399
+400        Arguments:
+401            text -- The text / Verbex object to look for.
+402
+403        Returns:
+404            Modified Verbex object.
+405        """
+406        return self.find(text)
+407
+408    @re_escape
+409    @beartype
+410    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+411        """Match if string is followed by text.
+412
+413        Positive lookahead
+414
+415        Returns:
+416            Modified Verbex object.
+417        """
+418        return self._add(f"(?={text})")
+419
+420    @re_escape
+421    @beartype
+422    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+423        """Match if string is not followed by text.
+424
+425        Negative lookahead
+426
+427        Returns:
+428            Modified Verbex object.
+429        """
+430        return self._add(f"(?!{text})")
+431
+432    @re_escape
+433    @beartype
+434    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+435        """Match if string is not preceded by text.
+436
+437        Positive lookbehind
+438
+439        Returns:
+440            Modified Verbex object.
+441        """
+442        return self._add(f"(?<={text})")
+443
+444    @re_escape
+445    @beartype
+446    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+447        """Match if string is not preceded by text.
+448
+449        Negative Lookbehind
+450
+451        Returns:
+452            Modified Verbex object.
+453        """
+454        return self._add(f"(?<!{text})")
+455
+456    # only allow CharclassOrChars
+457
+458    @re_escape
+459    @beartype
+460    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+461        """Find anything in this group of chars or char class.
+462
+463        Arguments:
+464            text -- The characters to look for.
+465
+466        Returns:
+467            Modified Verbex object.
+468        """
+469        return self._add(f"(?:[{chargroup}])")
+470
+471    @re_escape
+472    @beartype
+473    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+474        """Find anything but this group of chars or char class.
+475
+476        Arguments:
+477            text -- The characters to not look for.
+478
+479        Returns:
+480            Modified Verbex object.
+481        """
+482        return self._add(f"(?:[^{text}])")
+483
+484    @re_escape
+485    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+486        """Find anything one or more times but this group of chars or char class.
+487
+488        Arguments:
+489            text -- The characters to not look for.
+490
+491        Returns:
+492            Modified Verbex object.
+493        """
+494        return self._add(f"[^{chargroup}]+")
+495
+496    # no text input
+497
+498    def start_of_line(self) -> Verbex:
+499        """Find the start of the line.
+500
+501        Returns:
+502            Modified Verbex object.
+503        """
+504        return self.find(SpecialChar.START_OF_LINE)
+505
+506    def end_of_line(self) -> Verbex:
+507        """Find the end of the line.
+508
+509        Returns:
+510            Modified Verbex object.
+511        """
+512        return self.find(SpecialChar.END_OF_LINE)
+513
+514    def line_break(self) -> Verbex:
+515        """Find a line break.
+516
+517        Returns:
+518            Modified Verbex object.
+519        """
+520        return self.find(SpecialChar.LINEBREAK)
+521
+522    def tab(self) -> Verbex:
+523        """Find a tab.
+524
+525        Returns:
+526            Modified Verbex object.
+527        """
+528        return self.find(SpecialChar.TAB)
+529
+530    def anything(self) -> Verbex:
+531        """Find anything one or more time.
+532
+533        Returns:
+534            Modified Verbex object.
+535        """
+536        return self._add(".+")
+537
+538    def as_few(self) -> Verbex:
+539        """Modify previous search to not be greedy.
+540
+541        Returns:
+542            Modified Verbex object.
+543        """
+544        return self._add("?")
+545
+546    @beartype
+547    def number_range(self, start: int, end: int) -> Verbex:
+548        """Generate a range of numbers.
+549
+550        Arguments:
+551            start -- Start of the range
+552            end -- End of the range
+553
+554        Returns:
+555            Modified Verbex object.
+556        """
+557        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+558
+559    @beartype
+560    def letter_range(self, start: Char, end: Char) -> Verbex:
+561        """Generate a range of letters.
+562
+563        Arguments:
+564            start -- Start of the range
+565            end -- End of the range
+566
+567        Returns:
+568            Modified Verbex object.
+569        """
+570        return self._add(f"[{start}-{end}]")
+571
+572    def word(self) -> Verbex:
+573        """Find a word on word boundary.
+574
+575        Returns:
+576            Modified Verbex object.
+577        """
+578        return self._add("(\\b\\w+\\b)")
+579
+580    # # --------------- modifiers ------------------------
+581
+582    def with_any_case(self) -> Verbex:
+583        """Modify Verbex object to be case insensitive.
+584
+585        Returns:
+586            Modified Verbex object.
+587        """
+588        self._modifiers |= re.IGNORECASE
+589        return self
+590
+591    def search_by_line(self) -> Verbex:
+592        """Search each line, ^ and $ match begining and end of line respectively.
+593
+594        Returns:
+595            Modified Verbex object.
+596        """
+597        self._modifiers |= re.MULTILINE
+598        return self
+599
+600    def with_ascii(self) -> Verbex:
+601        """Match ascii instead of unicode.
+602
+603        Returns:
+604            Modified Verbex object.
+605        """
+606        self._modifiers |= re.ASCII
+607        return self
+608
+609
+610# left over notes from original version
+611# def __getattr__(self, attr):
+612#     """ any other function will be sent to the regex object """
+613#     regex = self.regex()
+614#     return getattr(regex, attr)
+615
+616# def replace(self, string, repl):
+617#     return self.sub(repl, string)
+618
+619
+620if __name__ == "__main__":
+621    pass
+
+ +
+ +
+
+
#   + + P = ~P +
+ + + + +
+
+
+ #   + +
@runtime_checkable
+ + class + HasIter(typing.Protocol): +
+ +
+ View Source +
48@runtime_checkable
+49class HasIter(Protocol):
+50    """Workaround for mypy P.args."""
+51
+52    def __iter__(self) -> Iterator[Any]:
+53        """Object can be iterated.
+54
+55        Yields:
+56            Next object.
+57        """
+58        ...
+
+ +
+ +

Workaround for mypy P.args.

+
-class EscapedText(str): - """Text that has been escaped for regex. - - Arguments: - str -- Extend the string class. - """ - def __new__(cls, value: str) -> EscapedText: - """Return a escaped regex string. +
+
#   + + + HasIter(*args, **kwargs) +
+ +
+ View Source +
1429def _no_init_or_replace_init(self, *args, **kwargs):
+1430    cls = type(self)
+1431
+1432    if cls._is_protocol:
+1433        raise TypeError('Protocols cannot be instantiated')
+1434
+1435    # Already using a custom `__init__`. No need to calculate correct
+1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
+1437    if cls.__init__ is not _no_init_or_replace_init:
+1438        return
+1439
+1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
+1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
+1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
+1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
+1444    # instantiation of the protocol subclass will thus use the new
+1445    # `__init__` and no longer call `_no_init_or_replace_init`.
+1446    for base in cls.__mro__:
+1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
+1448        if init is not _no_init_or_replace_init:
+1449            cls.__init__ = init
+1450            break
+1451    else:
+1452        # should not happen
+1453        cls.__init__ = object.__init__
+1454
+1455    cls.__init__(self, *args, **kwargs)
+
+ +
+ + + +
+
+
+
+ #   + +
@runtime_checkable
+ + class + HasItems(typing.Protocol): +
+ +
+ View Source +
63@runtime_checkable
+64class HasItems(Protocol):
+65    """Workaround for mypy P.kwargs."""
+66
+67    def items(self) -> Tuple[str, Any]:
+68        """Object has items method.
+69
+70        Returns:
+71            The dict of items.
+72        """
+73        ...
+
+ +
+ +

Workaround for mypy P.kwargs.

+
- Arguments: - value -- the string to escape - Returns: - _description_ - """ - return str.__new__(cls, re.escape(value)) +
+
#   + + + HasItems(*args, **kwargs) +
+ +
+ View Source +
1429def _no_init_or_replace_init(self, *args, **kwargs):
+1430    cls = type(self)
+1431
+1432    if cls._is_protocol:
+1433        raise TypeError('Protocols cannot be instantiated')
+1434
+1435    # Already using a custom `__init__`. No need to calculate correct
+1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
+1437    if cls.__init__ is not _no_init_or_replace_init:
+1438        return
+1439
+1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
+1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
+1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
+1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
+1444    # instantiation of the protocol subclass will thus use the new
+1445    # `__init__` and no longer call `_no_init_or_replace_init`.
+1446    for base in cls.__mro__:
+1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
+1448        if init is not _no_init_or_replace_init:
+1449            cls.__init__ = init
+1450            break
+1451    else:
+1452        # should not happen
+1453        cls.__init__ = object.__init__
+1454
+1455    cls.__init__(self, *args, **kwargs)
+
+ +
+ + + +
+
+
#   + + + def + items(self) -> tuple[str, typing.Any]: +
+ +
+ View Source +
67    def items(self) -> Tuple[str, Any]:
+68        """Object has items method.
+69
+70        Returns:
+71            The dict of items.
+72        """
+73        ...
+
+ +
+ +

Object has items method.

+ +

Returns: + The dict of items.

+
-def re_escape(func: Callable[P, R]) -> Callable[P, R]: - """Automatically escape any string parameters as EscapedText. +
+
+
+
+ #   + + + class + EscapedText(builtins.str): +
+ +
+ View Source +
76class EscapedText(str):
+77    """Text that has been escaped for regex.
+78
+79    Arguments:
+80        str -- Extend the string class.
+81    """
+82
+83    def __new__(cls, value: str) -> EscapedText:
+84        """Return a escaped regex string.
+85
+86        Arguments:
+87            value -- the string to escape
+88
+89        Returns:
+90            _description_
+91        """
+92        return str.__new__(cls, re.escape(value))
+
+ +
+ +

Text that has been escaped for regex.

+ +

Arguments: + str -- Extend the string class.

+
- Arguments: - func -- The function to decorate. - Returns: - The decorated function. - """ +
+
#   - @wraps(func) - def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore - escaped_args: List[Any] = [] - escaped_kwargs: Dict[str, Any] = {} - for arg in cast(HasIter, args): - if not isinstance(arg, EscapedText) and isinstance(arg, str): - escaped_args.append(EscapedText(arg)) - else: - escaped_args.append(arg) - arg_k: str - arg_v: Any - for arg_k, arg_v in cast(HasItems, kwargs).items(): - if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): - escaped_kwargs[arg_k] = EscapedText(str(arg_v)) - else: - escaped_kwargs[arg_k] = arg_v - return func(*escaped_args, **escaped_kwargs) # type: ignore + + EscapedText(value: str) +
- return inner +
+ View Source +
83    def __new__(cls, value: str) -> EscapedText:
+84        """Return a escaped regex string.
+85
+86        Arguments:
+87            value -- the string to escape
+88
+89        Returns:
+90            _description_
+91        """
+92        return str.__new__(cls, re.escape(value))
+
+
-class CharClass(Enum): - """Enum of character classes in regex. +

Return a escaped regex string.

- Arguments: - Enum -- Extends the Enum class. - """ +

Arguments: + value -- the string to escape

- DIGIT = "\\d" - LETTER = "\\w" - UPPERCASE_LETTER = "\\u" - LOWERCASE_LETTER = "\\l" - WHITESPACE = "\\s" - TAB = "\\t" +

Returns: + _description_

+
- def __str__(self) -> str: - """To string method based on Enum value. - - Returns: - value of Enum - """ - return self.value - - -class SpecialChar(Enum): - """Enum of special charaters, shorthand. - - Arguments: - Enum -- Extends the Enum class. - """ - - # does not work / should not be used in [ ] - LINEBREAK = "(\\n|(\\r\\n))" - START_OF_LINE = "^" - END_OF_LINE = "$" - TAB = "\t" - - def __str__(self) -> str: - """To string for special chars enum. - - Returns: - Return value of enum as string. - """ - return self.value - - -CharClassOrChars: TypeAlias = Union[str, CharClass] -EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] -VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] - - -class Verbex: - """ - VerbalExpressions class. - - the following methods do not try to match the original js lib! - """ - - EMPTY_REGEX_FLAG = re.RegexFlag(0) - - @re_escape - @beartype - def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): - """Create a Verbex object; setting any needed flags. - - Keyword Arguments: - modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) - """ - # self._parts: List[str] = [text] - self._parts: List[str] = [] - self._modifiers = modifiers - - @property - def modifiers(self) -> re.RegexFlag: - """Return the modifiers for this Verbex object. - - Returns: - The modifiers applied to this object. - """ - return self._modifiers - - def __str__(self) -> str: - """Return regex string representation.""" - return "".join(self._parts) - - @beartype - def _add(self, value: Union[str, List[str]]) -> Verbex: - """ - Append a transformed value to internal expression to be compiled. - - As possible, this method should be "private". - """ - if isinstance(value, list): - self._parts.extend(value) - else: - self._parts.append(value) - return self - - def regex(self) -> Pattern[str]: - """Get a regular expression object.""" - return re.compile( - str(self), - self._modifiers, - ) - - # allow VerbexEscapedCharClassOrSpecial - - @re_escape - @beartype - def _capture_group_with_name( - self, - name: str, - text: VerbexEscapedCharClassOrSpecial, - ) -> Verbex: - return self._add(f"(?<{name}>{str(text)})") - - @re_escape - @beartype - def _capture_group_without_name( - self, - text: VerbexEscapedCharClassOrSpecial, - ) -> Verbex: - return self._add(f"({str(text)})") - - @re_escape - @beartype - def capture_group( - self, - /, - name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, - text: Optional[VerbexEscapedCharClassOrSpecial] = None, - ) -> Verbex: - """Create a capture group. - - Name is optional if not specified then the first argument is the text. - - Keyword Arguments: - name_or_text -- The name of the group / text to search for (default: {None}) - text -- The text to search for (default: {None}) - - Raises: - ValueError: If name is specified then text must be as well. - - Returns: - Verbex with added capture group. - """ - if name_or_text is not None: - if text is None: - _text = name_or_text - return self._capture_group_without_name(_text) - if isinstance(name_or_text, str): - return self._capture_group_with_name(name_or_text, text) - raise ValueError("text must be specified with optional name") - - @re_escape - @beartype - def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 - """`or` is a python keyword so we use `OR` instead. - - Arguments: - text -- Text to find or a Verbex object. - - Returns: - Modified Verbex object. - """ - return self._add("|").find(text) - - @re_escape - @beartype - def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Find the text or Verbex object zero or more times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)})*") - - @re_escape - @beartype - def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Find the text or Verbex object one or more times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)})+") - - @re_escape - @beartype - def n_times( - self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 - ) -> Verbex: - """Find the text or Verbex object n or more times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n}}}") - - @re_escape - @beartype - def n_times_or_more( - self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 - ) -> Verbex: - """Find the text or Verbex object at least n times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n},}}") - - @re_escape - @beartype - def n_to_m_times( - self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 - m: int, # noqa: VNE001 - ) -> Verbex: - """Find the text or Verbex object between n and m times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n},{m}}}") - - @re_escape - @beartype - def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Possibly find the text / Verbex object. - - Arguments: - text -- The text / Verbex object to possibly find. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)})?") - - @re_escape - @beartype - def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Find the text or Verbex object. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(str(text)) - - @re_escape - @beartype - def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Synonym for find. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self.find(text) - - @re_escape - @beartype - def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Match if string is followed by text. - - Positive lookahead - - Returns: - Modified Verbex object. - """ - return self._add(f"(?={text})") - - @re_escape - @beartype - def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Match if string is not followed by text. - - Negative lookahead - - Returns: - Modified Verbex object. - """ - return self._add(f"(?!{text})") - - @re_escape - @beartype - def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Match if string is not preceded by text. - - Positive lookbehind - - Returns: - Modified Verbex object. - """ - return self._add(f"(?<={text})") - - @re_escape - @beartype - def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: - """Match if string is not preceded by text. - - Negative Lookbehind - - Returns: - Modified Verbex object. - """ - return self._add(f"(?<!{text})") - - # only allow CharclassOrChars - - @re_escape - @beartype - def any_of(self, chargroup: CharClassOrChars) -> Verbex: - """Find anything in this group of chars or char class. - Arguments: - text -- The characters to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:[{chargroup}])") - - @re_escape - @beartype - def not_any_of(self, text: CharClassOrChars) -> Verbex: - """Find anything but this group of chars or char class. +
+
+
Inherited Members
+
+
builtins.str
+
encode
+
replace
+
split
+
rsplit
+
join
+
capitalize
+
casefold
+
title
+
center
+
count
+
expandtabs
+
find
+
partition
+
index
+
ljust
+
lower
+
lstrip
+
rfind
+
rindex
+
rjust
+
rstrip
+
rpartition
+
splitlines
+
strip
+
swapcase
+
translate
+
upper
+
startswith
+
endswith
+
removeprefix
+
removesuffix
+
isascii
+
islower
+
isupper
+
istitle
+
isspace
+
isdecimal
+
isdigit
+
isnumeric
+
isalpha
+
isalnum
+
isidentifier
+
isprintable
+
zfill
+
format
+
format_map
+
maketrans
+ +
+
+
+
+
+
#   + + + def + re_escape( + func: collections.abc.Callable[~P, ~R] +) -> collections.abc.Callable[~P, ~R]: +
+ +
+ View Source +
 95def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+ 96    """Automatically escape any string parameters as EscapedText.
+ 97
+ 98    Arguments:
+ 99        func -- The function to decorate.
+100
+101    Returns:
+102        The decorated function.
+103    """
+104
+105    @wraps(func)
+106    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+107        escaped_args: List[Any] = []
+108        escaped_kwargs: Dict[str, Any] = {}
+109        for arg in cast(HasIter, args):
+110            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+111                escaped_args.append(EscapedText(arg))
+112            else:
+113                escaped_args.append(arg)
+114        arg_k: str
+115        arg_v: Any
+116        for arg_k, arg_v in cast(HasItems, kwargs).items():
+117            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+118                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+119            else:
+120                escaped_kwargs[arg_k] = arg_v
+121        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+122
+123    return inner
+
+ +
+ +

Automatically escape any string parameters as EscapedText.

+ +

Arguments: + func -- The function to decorate.

+ +

Returns: + The decorated function.

+
- Arguments: - text -- The characters to not look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:[^{text}])") - @re_escape - def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: - """Find anything one or more times but this group of chars or char class. +
+
+
+ #   + + + class + CharClass(enum.Enum): +
+ +
+ View Source +
126class CharClass(Enum):
+127    """Enum of character classes in regex.
+128
+129    Arguments:
+130        Enum -- Extends the Enum class.
+131    """
+132
+133    DIGIT = "\\d"
+134    LETTER = "\\w"
+135    UPPERCASE_LETTER = "\\u"
+136    LOWERCASE_LETTER = "\\l"
+137    WHITESPACE = "\\s"
+138    TAB = "\\t"
+139
+140    def __str__(self) -> str:
+141        """To string method based on Enum value.
+142
+143        Returns:
+144            value of Enum
+145        """
+146        return self.value
+
+ +
+ +

Enum of character classes in regex.

+ +

Arguments: + Enum -- Extends the Enum class.

+
- Arguments: - text -- The characters to not look for. - Returns: - Modified Verbex object. - """ - return self._add(f"[^{chargroup}]+") +
+
#   + + DIGIT = <CharClass.DIGIT: '\\d'> +
+ + + + +
+
+
#   + + LETTER = <CharClass.LETTER: '\\w'> +
+ + + + +
+
+
#   + + UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'> +
+ + + + +
+
+
#   + + LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'> +
+ + + + +
+
+
#   + + WHITESPACE = <CharClass.WHITESPACE: '\\s'> +
+ + + + +
+
+
#   + + TAB = <CharClass.TAB: '\\t'> +
+ + + + +
+
+
Inherited Members
+
+
enum.Enum
+
name
+
value
+ +
+
+
+
+
+
+ #   + + + class + SpecialChar(enum.Enum): +
+ +
+ View Source +
149class SpecialChar(Enum):
+150    """Enum of special charaters, shorthand.
+151
+152    Arguments:
+153        Enum -- Extends the Enum class.
+154    """
+155
+156    # does not work  / should not be used in [ ]
+157    LINEBREAK = "(\\n|(\\r\\n))"
+158    START_OF_LINE = "^"
+159    END_OF_LINE = "$"
+160    TAB = "\t"
+161
+162    def __str__(self) -> str:
+163        """To string for special chars enum.
+164
+165        Returns:
+166            Return value of enum as string.
+167        """
+168        return self.value
+
+ +
+ +

Enum of special charaters, shorthand.

+ +

Arguments: + Enum -- Extends the Enum class.

+
- # no text input - def start_of_line(self) -> Verbex: - """Find the start of the line. +
+
#   - Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.START_OF_LINE) + LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'> +
+ + + + +
+
+
#   + + START_OF_LINE = <SpecialChar.START_OF_LINE: '^'> +
+ + + + +
+
+
#   + + END_OF_LINE = <SpecialChar.END_OF_LINE: '$'> +
+ + + + +
+
+
#   + + TAB = <SpecialChar.TAB: '\t'> +
+ + + + +
+
+
Inherited Members
+
+
enum.Enum
+
name
+
value
+ +
+
+
+
+
+
#   + + CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass] +
+ + + + +
+
+
#   + + EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +
+ + + + +
+
+
#   + + VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +
+ + + + +
+
+
+ #   + + + class + Verbex: +
+ +
+ View Source +
176class Verbex:
+177    """
+178    VerbalExpressions class.
+179
+180    the following methods do not try to match the original js lib!
+181    """
+182
+183    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+184
+185    @re_escape
+186    @beartype
+187    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+188        """Create a Verbex object; setting any needed flags.
+189
+190        Keyword Arguments:
+191            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+192        """
+193        # self._parts: List[str] = [text]
+194        self._parts: List[str] = []
+195        self._modifiers = modifiers
+196
+197    @property
+198    def modifiers(self) -> re.RegexFlag:
+199        """Return the modifiers for this Verbex object.
+200
+201        Returns:
+202            The modifiers applied to this object.
+203        """
+204        return self._modifiers
+205
+206    def __str__(self) -> str:
+207        """Return regex string representation."""
+208        return "".join(self._parts)
+209
+210    @beartype
+211    def _add(self, value: Union[str, List[str]]) -> Verbex:
+212        """
+213        Append a transformed value to internal expression to be compiled.
+214
+215        As possible, this method should be "private".
+216        """
+217        if isinstance(value, list):
+218            self._parts.extend(value)
+219        else:
+220            self._parts.append(value)
+221        return self
+222
+223    def regex(self) -> Pattern[str]:
+224        """Get a regular expression object."""
+225        return re.compile(
+226            str(self),
+227            self._modifiers,
+228        )
+229
+230    # allow VerbexEscapedCharClassOrSpecial
+231
+232    @re_escape
+233    @beartype
+234    def _capture_group_with_name(
+235        self,
+236        name: str,
+237        text: VerbexEscapedCharClassOrSpecial,
+238    ) -> Verbex:
+239        return self._add(f"(?<{name}>{str(text)})")
+240
+241    @re_escape
+242    @beartype
+243    def _capture_group_without_name(
+244        self,
+245        text: VerbexEscapedCharClassOrSpecial,
+246    ) -> Verbex:
+247        return self._add(f"({str(text)})")
+248
+249    @re_escape
+250    @beartype
+251    def capture_group(
+252        self,
+253        /,
+254        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+255        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+256    ) -> Verbex:
+257        """Create a capture group.
+258
+259        Name is optional if not specified then the first argument is the text.
+260
+261        Keyword Arguments:
+262            name_or_text -- The name of the group / text to search for (default: {None})
+263            text -- The text to search for (default: {None})
+264
+265        Raises:
+266            ValueError: If name is specified then text must be as well.
+267
+268        Returns:
+269            Verbex with added capture group.
+270        """
+271        if name_or_text is not None:
+272            if text is None:
+273                _text = name_or_text
+274                return self._capture_group_without_name(_text)
+275            if isinstance(name_or_text, str):
+276                return self._capture_group_with_name(name_or_text, text)
+277        raise ValueError("text must be specified with optional name")
+278
+279    @re_escape
+280    @beartype
+281    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+282        """`or` is a python keyword so we use `OR` instead.
+283
+284        Arguments:
+285            text -- Text to find or a Verbex object.
+286
+287        Returns:
+288            Modified Verbex object.
+289        """
+290        return self._add("|").find(text)
+291
+292    @re_escape
+293    @beartype
+294    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+295        """Find the text or Verbex object zero or more times.
+296
+297        Arguments:
+298            text -- The text / Verbex object to look for.
+299
+300        Returns:
+301            Modified Verbex object.
+302        """
+303        return self._add(f"(?:{str(text)})*")
+304
+305    @re_escape
+306    @beartype
+307    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+308        """Find the text or Verbex object one or more times.
+309
+310        Arguments:
+311            text -- The text / Verbex object to look for.
+312
+313        Returns:
+314            Modified Verbex object.
+315        """
+316        return self._add(f"(?:{str(text)})+")
+317
+318    @re_escape
+319    @beartype
+320    def n_times(
+321        self,
+322        text: VerbexEscapedCharClassOrSpecial,
+323        n: int,  # noqa: VNE001
+324    ) -> Verbex:
+325        """Find the text or Verbex object n or more times.
+326
+327        Arguments:
+328            text -- The text / Verbex object to look for.
+329
+330        Returns:
+331            Modified Verbex object.
+332        """
+333        return self._add(f"(?:{str(text)}){{{n}}}")
+334
+335    @re_escape
+336    @beartype
+337    def n_times_or_more(
+338        self,
+339        text: VerbexEscapedCharClassOrSpecial,
+340        n: int,  # noqa: VNE001
+341    ) -> Verbex:
+342        """Find the text or Verbex object at least n times.
+343
+344        Arguments:
+345            text -- The text / Verbex object to look for.
+346
+347        Returns:
+348            Modified Verbex object.
+349        """
+350        return self._add(f"(?:{str(text)}){{{n},}}")
+351
+352    @re_escape
+353    @beartype
+354    def n_to_m_times(
+355        self,
+356        text: VerbexEscapedCharClassOrSpecial,
+357        n: int,  # noqa: VNE001
+358        m: int,  # noqa: VNE001
+359    ) -> Verbex:
+360        """Find the text or Verbex object between n and m times.
+361
+362        Arguments:
+363            text -- The text / Verbex object to look for.
+364
+365        Returns:
+366            Modified Verbex object.
+367        """
+368        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+369
+370    @re_escape
+371    @beartype
+372    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+373        """Possibly find the text / Verbex object.
+374
+375        Arguments:
+376            text -- The text / Verbex object to possibly find.
+377
+378        Returns:
+379            Modified Verbex object.
+380        """
+381        return self._add(f"(?:{str(text)})?")
+382
+383    @re_escape
+384    @beartype
+385    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+386        """Find the text or Verbex object.
+387
+388        Arguments:
+389            text -- The text / Verbex object to look for.
+390
+391        Returns:
+392            Modified Verbex object.
+393        """
+394        return self._add(str(text))
+395
+396    @re_escape
+397    @beartype
+398    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+399        """Synonym for find.
+400
+401        Arguments:
+402            text -- The text / Verbex object to look for.
+403
+404        Returns:
+405            Modified Verbex object.
+406        """
+407        return self.find(text)
+408
+409    @re_escape
+410    @beartype
+411    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+412        """Match if string is followed by text.
+413
+414        Positive lookahead
+415
+416        Returns:
+417            Modified Verbex object.
+418        """
+419        return self._add(f"(?={text})")
+420
+421    @re_escape
+422    @beartype
+423    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+424        """Match if string is not followed by text.
+425
+426        Negative lookahead
+427
+428        Returns:
+429            Modified Verbex object.
+430        """
+431        return self._add(f"(?!{text})")
+432
+433    @re_escape
+434    @beartype
+435    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+436        """Match if string is not preceded by text.
+437
+438        Positive lookbehind
+439
+440        Returns:
+441            Modified Verbex object.
+442        """
+443        return self._add(f"(?<={text})")
+444
+445    @re_escape
+446    @beartype
+447    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+448        """Match if string is not preceded by text.
+449
+450        Negative Lookbehind
+451
+452        Returns:
+453            Modified Verbex object.
+454        """
+455        return self._add(f"(?<!{text})")
+456
+457    # only allow CharclassOrChars
+458
+459    @re_escape
+460    @beartype
+461    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+462        """Find anything in this group of chars or char class.
+463
+464        Arguments:
+465            text -- The characters to look for.
+466
+467        Returns:
+468            Modified Verbex object.
+469        """
+470        return self._add(f"(?:[{chargroup}])")
+471
+472    @re_escape
+473    @beartype
+474    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+475        """Find anything but this group of chars or char class.
+476
+477        Arguments:
+478            text -- The characters to not look for.
+479
+480        Returns:
+481            Modified Verbex object.
+482        """
+483        return self._add(f"(?:[^{text}])")
+484
+485    @re_escape
+486    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+487        """Find anything one or more times but this group of chars or char class.
+488
+489        Arguments:
+490            text -- The characters to not look for.
+491
+492        Returns:
+493            Modified Verbex object.
+494        """
+495        return self._add(f"[^{chargroup}]+")
+496
+497    # no text input
+498
+499    def start_of_line(self) -> Verbex:
+500        """Find the start of the line.
+501
+502        Returns:
+503            Modified Verbex object.
+504        """
+505        return self.find(SpecialChar.START_OF_LINE)
+506
+507    def end_of_line(self) -> Verbex:
+508        """Find the end of the line.
+509
+510        Returns:
+511            Modified Verbex object.
+512        """
+513        return self.find(SpecialChar.END_OF_LINE)
+514
+515    def line_break(self) -> Verbex:
+516        """Find a line break.
+517
+518        Returns:
+519            Modified Verbex object.
+520        """
+521        return self.find(SpecialChar.LINEBREAK)
+522
+523    def tab(self) -> Verbex:
+524        """Find a tab.
+525
+526        Returns:
+527            Modified Verbex object.
+528        """
+529        return self.find(SpecialChar.TAB)
+530
+531    def anything(self) -> Verbex:
+532        """Find anything one or more time.
+533
+534        Returns:
+535            Modified Verbex object.
+536        """
+537        return self._add(".+")
+538
+539    def as_few(self) -> Verbex:
+540        """Modify previous search to not be greedy.
+541
+542        Returns:
+543            Modified Verbex object.
+544        """
+545        return self._add("?")
+546
+547    @beartype
+548    def number_range(self, start: int, end: int) -> Verbex:
+549        """Generate a range of numbers.
+550
+551        Arguments:
+552            start -- Start of the range
+553            end -- End of the range
+554
+555        Returns:
+556            Modified Verbex object.
+557        """
+558        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+559
+560    @beartype
+561    def letter_range(self, start: Char, end: Char) -> Verbex:
+562        """Generate a range of letters.
+563
+564        Arguments:
+565            start -- Start of the range
+566            end -- End of the range
+567
+568        Returns:
+569            Modified Verbex object.
+570        """
+571        return self._add(f"[{start}-{end}]")
+572
+573    def word(self) -> Verbex:
+574        """Find a word on word boundary.
+575
+576        Returns:
+577            Modified Verbex object.
+578        """
+579        return self._add("(\\b\\w+\\b)")
+580
+581    # # --------------- modifiers ------------------------
+582
+583    def with_any_case(self) -> Verbex:
+584        """Modify Verbex object to be case insensitive.
+585
+586        Returns:
+587            Modified Verbex object.
+588        """
+589        self._modifiers |= re.IGNORECASE
+590        return self
+591
+592    def search_by_line(self) -> Verbex:
+593        """Search each line, ^ and $ match begining and end of line respectively.
+594
+595        Returns:
+596            Modified Verbex object.
+597        """
+598        self._modifiers |= re.MULTILINE
+599        return self
+600
+601    def with_ascii(self) -> Verbex:
+602        """Match ascii instead of unicode.
+603
+604        Returns:
+605            Modified Verbex object.
+606        """
+607        self._modifiers |= re.ASCII
+608        return self
+
+ +
+ +

VerbalExpressions class.

- def end_of_line(self) -> Verbex: - """Find the end of the line. +

the following methods do not try to match the original js lib!

+
- Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.END_OF_LINE) - def line_break(self) -> Verbex: - """Find a line break. +
+
#   - Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.LINEBREAK) +
@re_escape
+
@beartype
- def tab(self) -> Verbex: - """Find a tab. + Verbex(modifiers: re.RegexFlag = ) +
+ +
+ View Source +
185    @re_escape
+186    @beartype
+187    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+188        """Create a Verbex object; setting any needed flags.
+189
+190        Keyword Arguments:
+191            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+192        """
+193        # self._parts: List[str] = [text]
+194        self._parts: List[str] = []
+195        self._modifiers = modifiers
+
+ +
+ +

Create a Verbex object; setting any needed flags.

- Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.TAB) - - def anything(self) -> Verbex: - """Find anything one or more time. - - Returns: - Modified Verbex object. - """ - return self._add(".+") - - def as_few(self) -> Verbex: - """Modify previous search to not be greedy. - - Returns: - Modified Verbex object. - """ - return self._add("?") - - @beartype - def number_range(self, start: int, end: int) -> Verbex: - """Generate a range of numbers. - - Arguments: - start -- Start of the range - end -- End of the range - - Returns: - Modified Verbex object. - """ - return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") - - @beartype - def letter_range(self, start: Char, end: Char) -> Verbex: - """Generate a range of letters. - - Arguments: - start -- Start of the range - end -- End of the range - - Returns: - Modified Verbex object. - """ - return self._add(f"[{start}-{end}]") - - def word(self) -> Verbex: - """Find a word on word boundary. - - Returns: - Modified Verbex object. - """ - return self._add("(\\b\\w+\\b)") - - # # --------------- modifiers ------------------------ - - def with_any_case(self) -> Verbex: - """Modify Verbex object to be case insensitive. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.IGNORECASE - return self - - def search_by_line(self) -> Verbex: - """Search each line, ^ and $ match begining and end of line respectively. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.MULTILINE - return self - - def with_ascii(self) -> Verbex: - """Match ascii instead of unicode. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.ASCII - return self - - -# left over notes from original version -# def __getattr__(self, attr): -# """ any other function will be sent to the regex object """ -# regex = self.regex() -# return getattr(regex, attr) - -# def replace(self, string, repl): -# return self.sub(repl, string) - - -if __name__ == "__main__": - pass
-
-
-
-
-
-
-
-

Functions

-
-
-def re_escape(func: Callable[P, R]) ‑> collections.abc.Callable[~P, ~R] -
-
-

Automatically escape any string parameters as EscapedText.

-

Arguments

-

func – The function to decorate.

-

Returns

-

The decorated function.

-
- -Expand source code - -
def re_escape(func: Callable[P, R]) -> Callable[P, R]:
-    """Automatically escape any string parameters as EscapedText.
-
-    Arguments:
-        func -- The function to decorate.
-
-    Returns:
-        The decorated function.
-    """
-
-    @wraps(func)
-    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-        escaped_args: List[Any] = []
-        escaped_kwargs: Dict[str, Any] = {}
-        for arg in cast(HasIter, args):
-            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-                escaped_args.append(EscapedText(arg))
-            else:
-                escaped_args.append(arg)
-        arg_k: str
-        arg_v: Any
-        for arg_k, arg_v in cast(HasItems, kwargs).items():
-            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-            else:
-                escaped_kwargs[arg_k] = arg_v
-        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-
-    return inner
-
-
-
-
-
-

Classes

-
-
-class CharClass -(value, names=None, *, module=None, qualname=None, type=None, start=1) -
-
-

Enum of character classes in regex.

-

Arguments

-

Enum – Extends the Enum class.

-
- -Expand source code - -
class CharClass(Enum):
-    """Enum of character classes in regex.
-
-    Arguments:
-        Enum -- Extends the Enum class.
-    """
-
-    DIGIT = "\\d"
-    LETTER = "\\w"
-    UPPERCASE_LETTER = "\\u"
-    LOWERCASE_LETTER = "\\l"
-    WHITESPACE = "\\s"
-    TAB = "\\t"
-
-    def __str__(self) -> str:
-        """To string method based on Enum value.
-
-        Returns:
-            value of Enum
-        """
-        return self.value
-
-

Ancestors

-
    -
  • enum.Enum
  • -
-

Class variables

-
-
var DIGIT
-
-
-
-
var LETTER
-
-
-
-
var LOWERCASE_LETTER
-
-
-
-
var TAB
-
-
-
-
var UPPERCASE_LETTER
-
-
-
-
var WHITESPACE
-
-
-
-
-
-
-class EscapedText -(value: str) -
-
-

Text that has been escaped for regex.

-

Arguments

-

str – Extend the string class.

-
- -Expand source code - -
class EscapedText(str):
-    """Text that has been escaped for regex.
-
-    Arguments:
-        str -- Extend the string class.
-    """
-
-    def __new__(cls, value: str) -> EscapedText:
-        """Return a escaped regex string.
-
-        Arguments:
-            value -- the string to escape
-
-        Returns:
-            _description_
-        """
-        return str.__new__(cls, re.escape(value))
-
-

Ancestors

-
    -
  • builtins.str
  • -
-
-
-class HasItems -(*args, **kwargs) -
-
-

Workaround for mypy P.kwargs.

-
- -Expand source code - -
@runtime_checkable
-class HasItems(Protocol):
-    """Workaround for mypy P.kwargs."""
-
-    def items(self) -> Tuple[str, Any]:
-        """Object has items method.
-
-        Returns:
-            The dict of items.
-        """
-        ...
-
-

Ancestors

-
    -
  • typing.Protocol
  • -
  • typing.Generic
  • -
-

Methods

-
-
-def items(self) ‑> tuple[str, typing.Any] -
-
-

Object has items method.

-

Returns

-

The dict of items.

-
- -Expand source code - -
def items(self) -> Tuple[str, Any]:
-    """Object has items method.
-
-    Returns:
-        The dict of items.
-    """
-    ...
-
-
-
-
-
-class HasIter -(*args, **kwargs) -
-
-

Workaround for mypy P.args.

-
- -Expand source code - -
@runtime_checkable
-class HasIter(Protocol):
-    """Workaround for mypy P.args."""
-
-    def __iter__(self) -> Iterator[Any]:
-        """Object can be iterated.
-
-        Yields:
-            Next object.
-        """
-        ...
-
-

Ancestors

-
    -
  • typing.Protocol
  • -
  • typing.Generic
  • -
-
-
-class SpecialChar -(value, names=None, *, module=None, qualname=None, type=None, start=1) -
-
-

Enum of special charaters, shorthand.

-

Arguments

-

Enum – Extends the Enum class.

-
- -Expand source code - -
class SpecialChar(Enum):
-    """Enum of special charaters, shorthand.
-
-    Arguments:
-        Enum -- Extends the Enum class.
-    """
-
-    # does not work  / should not be used in [ ]
-    LINEBREAK = "(\\n|(\\r\\n))"
-    START_OF_LINE = "^"
-    END_OF_LINE = "$"
-    TAB = "\t"
-
-    def __str__(self) -> str:
-        """To string for special chars enum.
-
-        Returns:
-            Return value of enum as string.
-        """
-        return self.value
-
-

Ancestors

-
    -
  • enum.Enum
  • -
-

Class variables

-
-
var END_OF_LINE
-
-
-
-
var LINEBREAK
-
-
-
-
var START_OF_LINE
-
-
-
-
var TAB
-
-
-
-
-
-
-class Verbex -(modifiers: re.RegexFlag = ) -
-
-

VerbalExpressions class.

-

the following methods do not try to match the original js lib!

-

Create a Verbex object; setting any needed flags.

Keyword Arguments: -modifiers – Regex modifying flags (default: {re.RegexFlag(0)})

-
- -Expand source code - -
class Verbex:
-    """
-    VerbalExpressions class.
-
-    the following methods do not try to match the original js lib!
-    """
-
-    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-
-    @re_escape
-    @beartype
-    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-        """Create a Verbex object; setting any needed flags.
-
-        Keyword Arguments:
-            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-        """
-        # self._parts: List[str] = [text]
-        self._parts: List[str] = []
-        self._modifiers = modifiers
-
-    @property
-    def modifiers(self) -> re.RegexFlag:
-        """Return the modifiers for this Verbex object.
-
-        Returns:
-            The modifiers applied to this object.
-        """
-        return self._modifiers
-
-    def __str__(self) -> str:
-        """Return regex string representation."""
-        return "".join(self._parts)
-
-    @beartype
-    def _add(self, value: Union[str, List[str]]) -> Verbex:
-        """
-        Append a transformed value to internal expression to be compiled.
-
-        As possible, this method should be "private".
-        """
-        if isinstance(value, list):
-            self._parts.extend(value)
-        else:
-            self._parts.append(value)
-        return self
-
-    def regex(self) -> Pattern[str]:
-        """Get a regular expression object."""
-        return re.compile(
-            str(self),
-            self._modifiers,
-        )
-
-    # allow VerbexEscapedCharClassOrSpecial
-
-    @re_escape
-    @beartype
-    def _capture_group_with_name(
-        self,
-        name: str,
-        text: VerbexEscapedCharClassOrSpecial,
-    ) -> Verbex:
-        return self._add(f"(?<{name}>{str(text)})")
-
-    @re_escape
-    @beartype
-    def _capture_group_without_name(
-        self,
-        text: VerbexEscapedCharClassOrSpecial,
-    ) -> Verbex:
-        return self._add(f"({str(text)})")
-
-    @re_escape
-    @beartype
-    def capture_group(
-        self,
-        /,
-        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-    ) -> Verbex:
-        """Create a capture group.
-
-        Name is optional if not specified then the first argument is the text.
-
-        Keyword Arguments:
-            name_or_text -- The name of the group / text to search for (default: {None})
-            text -- The text to search for (default: {None})
-
-        Raises:
-            ValueError: If name is specified then text must be as well.
-
-        Returns:
-            Verbex with added capture group.
-        """
-        if name_or_text is not None:
-            if text is None:
-                _text = name_or_text
-                return self._capture_group_without_name(_text)
-            if isinstance(name_or_text, str):
-                return self._capture_group_with_name(name_or_text, text)
-        raise ValueError("text must be specified with optional name")
-
-    @re_escape
-    @beartype
-    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-        """`or` is a python keyword so we use `OR` instead.
-
-        Arguments:
-            text -- Text to find or a Verbex object.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add("|").find(text)
-
-    @re_escape
-    @beartype
-    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Find the text or Verbex object zero or more times.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)})*")
-
-    @re_escape
-    @beartype
-    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Find the text or Verbex object one or more times.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)})+")
-
-    @re_escape
-    @beartype
-    def n_times(
-        self,
-        text: VerbexEscapedCharClassOrSpecial,
-        n: int,  # noqa: VNE001
-    ) -> Verbex:
-        """Find the text or Verbex object n or more times.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)}){{{n}}}")
-
-    @re_escape
-    @beartype
-    def n_times_or_more(
-        self,
-        text: VerbexEscapedCharClassOrSpecial,
-        n: int,  # noqa: VNE001
-    ) -> Verbex:
-        """Find the text or Verbex object at least n times.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)}){{{n},}}")
-
-    @re_escape
-    @beartype
-    def n_to_m_times(
-        self,
-        text: VerbexEscapedCharClassOrSpecial,
-        n: int,  # noqa: VNE001
-        m: int,  # noqa: VNE001
-    ) -> Verbex:
-        """Find the text or Verbex object between n and m times.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-
-    @re_escape
-    @beartype
-    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Possibly find the text / Verbex object.
-
-        Arguments:
-            text -- The text / Verbex object to possibly find.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?:{str(text)})?")
-
-    @re_escape
-    @beartype
-    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Find the text or Verbex object.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(str(text))
-
-    @re_escape
-    @beartype
-    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Synonym for find.
-
-        Arguments:
-            text -- The text / Verbex object to look for.
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self.find(text)
-
-    @re_escape
-    @beartype
-    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Match if string is followed by text.
-
-        Positive lookahead
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?={text})")
-
-    @re_escape
-    @beartype
-    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Match if string is not followed by text.
-
-        Negative lookahead
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?!{text})")
-
-    @re_escape
-    @beartype
-    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Match if string is not preceded by text.
-
-        Positive lookbehind
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?<={text})")
-
-    @re_escape
-    @beartype
-    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-        """Match if string is not preceded by text.
-
-        Negative Lookbehind
-
-        Returns:
-            Modified Verbex object.
-        """
-        return self._add(f"(?<!{text})")
-
-    # only allow CharclassOrChars
-
-    @re_escape
-    @beartype
-    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-        """Find anything in this group of chars or char class.
+    modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

+ + + + +
+
#   - Arguments: - text -- The characters to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:[{chargroup}])") - - @re_escape - @beartype - def not_any_of(self, text: CharClassOrChars) -> Verbex: - """Find anything but this group of chars or char class. + EMPTY_REGEX_FLAG = +
- Arguments: - text -- The characters to not look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:[^{text}])") + + - @re_escape - def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: - """Find anything one or more times but this group of chars or char class. +
+
+
#   + + modifiers: re.RegexFlag +
+ + +

Return the modifiers for this Verbex object.

+ +

Returns: + The modifiers applied to this object.

+
- Arguments: - text -- The characters to not look for. - Returns: - Modified Verbex object. - """ - return self._add(f"[^{chargroup}]+") +
+
+
#   - # no text input + + def + regex(self) -> Pattern[str]: +
- def start_of_line(self) -> Verbex: - """Find the start of the line. +
+ View Source +
223    def regex(self) -> Pattern[str]:
+224        """Get a regular expression object."""
+225        return re.compile(
+226            str(self),
+227            self._modifiers,
+228        )
+
- Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.START_OF_LINE) +
+ +

Get a regular expression object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + capture_group( + self, + /, + name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
249    @re_escape
+250    @beartype
+251    def capture_group(
+252        self,
+253        /,
+254        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+255        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+256    ) -> Verbex:
+257        """Create a capture group.
+258
+259        Name is optional if not specified then the first argument is the text.
+260
+261        Keyword Arguments:
+262            name_or_text -- The name of the group / text to search for (default: {None})
+263            text -- The text to search for (default: {None})
+264
+265        Raises:
+266            ValueError: If name is specified then text must be as well.
+267
+268        Returns:
+269            Verbex with added capture group.
+270        """
+271        if name_or_text is not None:
+272            if text is None:
+273                _text = name_or_text
+274                return self._capture_group_without_name(_text)
+275            if isinstance(name_or_text, str):
+276                return self._capture_group_with_name(name_or_text, text)
+277        raise ValueError("text must be specified with optional name")
+
+ +
+ +

Create a capture group.

- def end_of_line(self) -> Verbex: - """Find the end of the line. - - Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.END_OF_LINE) - - def line_break(self) -> Verbex: - """Find a line break. - - Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.LINEBREAK) - - def tab(self) -> Verbex: - """Find a tab. - - Returns: - Modified Verbex object. - """ - return self.find(SpecialChar.TAB) - - def anything(self) -> Verbex: - """Find anything one or more time. - - Returns: - Modified Verbex object. - """ - return self._add(".+") - - def as_few(self) -> Verbex: - """Modify previous search to not be greedy. - - Returns: - Modified Verbex object. - """ - return self._add("?") - - @beartype - def number_range(self, start: int, end: int) -> Verbex: - """Generate a range of numbers. - - Arguments: - start -- Start of the range - end -- End of the range - - Returns: - Modified Verbex object. - """ - return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") - - @beartype - def letter_range(self, start: Char, end: Char) -> Verbex: - """Generate a range of letters. - - Arguments: - start -- Start of the range - end -- End of the range - - Returns: - Modified Verbex object. - """ - return self._add(f"[{start}-{end}]") - - def word(self) -> Verbex: - """Find a word on word boundary. - - Returns: - Modified Verbex object. - """ - return self._add("(\\b\\w+\\b)") - - # # --------------- modifiers ------------------------ - - def with_any_case(self) -> Verbex: - """Modify Verbex object to be case insensitive. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.IGNORECASE - return self - - def search_by_line(self) -> Verbex: - """Search each line, ^ and $ match begining and end of line respectively. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.MULTILINE - return self - - def with_ascii(self) -> Verbex: - """Match ascii instead of unicode. - - Returns: - Modified Verbex object. - """ - self._modifiers |= re.ASCII - return self
-
-

Class variables

-
-
var EMPTY_REGEX_FLAG
-
-
-
-
-

Instance variables

-
-
var modifiers : re.RegexFlag
-
-

Return the modifiers for this Verbex object.

-

Returns

-

The modifiers applied to this object.

-
- -Expand source code - -
@property
-def modifiers(self) -> re.RegexFlag:
-    """Return the modifiers for this Verbex object.
-
-    Returns:
-        The modifiers applied to this object.
-    """
-    return self._modifiers
-
-
-
-

Methods

-
-
-def OR(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

or is a python keyword so we use OR instead.

-

Arguments

-

text – Text to find or a Verbex object.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-    """`or` is a python keyword so we use `OR` instead.
-
-    Arguments:
-        text -- Text to find or a Verbex object.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add("|").find(text)
-
-
-
-def any_of(self, chargroup: Union[str, CharClass]) ‑> Verbex -
-
-

Find anything in this group of chars or char class.

-

Arguments

-

text – The characters to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-    """Find anything in this group of chars or char class.
-
-    Arguments:
-        text -- The characters to look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?:[{chargroup}])")
-
-
-
-def anything(self) ‑> Verbex -
-
-

Find anything one or more time.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def anything(self) -> Verbex:
-    """Find anything one or more time.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(".+")
-
-
-
-def anything_but(self, chargroup: EscapedCharClassOrSpecial) ‑> Verbex -
-
-

Find anything one or more times but this group of chars or char class.

-

Arguments

-

text – The characters to not look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-    """Find anything one or more times but this group of chars or char class.
-
-    Arguments:
-        text -- The characters to not look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"[^{chargroup}]+")
-
-
-
-def as_few(self) ‑> Verbex -
-
-

Modify previous search to not be greedy.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def as_few(self) -> Verbex:
-    """Modify previous search to not be greedy.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add("?")
-
-
-
-def capture_group(self, /, name_or_text: Union[str, ForwardRef(None), ForwardRef('Verbex'), CharClassSpecialChar] = None, text: Union[str, ForwardRef(None), ForwardRef('Verbex'), CharClassSpecialChar] = None) ‑> Verbex -
-
-

Create a capture group.

Name is optional if not specified then the first argument is the text.

+

Keyword Arguments: -name_or_text – The name of the group / text to search for (default: {None}) -text – The text to search for (default: {None})

-

Raises

-
-
ValueError
-
If name is specified then text must be as well.
-
-

Returns

-

Verbex with added capture group.

-
- -Expand source code - -
@re_escape
-@beartype
-def capture_group(
+    name_or_text -- The name of the group / text to search for (default: {None})
+    text -- The text to search for (default: {None})

+ +

Raises: + ValueError: If name is specified then text must be as well.

+ +

Returns: + Verbex with added capture group.

+ + + + +
+
#   + +
@re_escape
+
@beartype
+ + def + OR( self, - /, - name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, - text: Optional[VerbexEscapedCharClassOrSpecial] = None, -) -> Verbex: - """Create a capture group. - - Name is optional if not specified then the first argument is the text. - - Keyword Arguments: - name_or_text -- The name of the group / text to search for (default: {None}) - text -- The text to search for (default: {None}) - - Raises: - ValueError: If name is specified then text must be as well. - - Returns: - Verbex with added capture group. - """ - if name_or_text is not None: - if text is None: - _text = name_or_text - return self._capture_group_without_name(_text) - if isinstance(name_or_text, str): - return self._capture_group_with_name(name_or_text, text) - raise ValueError("text must be specified with optional name")
-
-
-
-def end_of_line(self) ‑> Verbex -
-
-

Find the end of the line.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def end_of_line(self) -> Verbex:
-    """Find the end of the line.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self.find(SpecialChar.END_OF_LINE)
-
-
-
-def find(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Find the text or Verbex object.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Find the text or Verbex object.
-
-    Arguments:
-        text -- The text / Verbex object to look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(str(text))
-
-
-
-def followed_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Match if string is followed by text.

-

Positive lookahead

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Match if string is followed by text.
-
-    Positive lookahead
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?={text})")
-
-
-
-def letter_range(self, start: typing.Annotated[str, Is[_string_len_is_1]], end: typing.Annotated[str, Is[_string_len_is_1]]) ‑> Verbex -
-
-

Generate a range of letters.

-

Arguments

-

start – Start of the range -end – End of the range

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@beartype
-def letter_range(self, start: Char, end: Char) -> Verbex:
-    """Generate a range of letters.
-
-    Arguments:
-        start -- Start of the range
-        end -- End of the range
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"[{start}-{end}]")
-
-
-
-def line_break(self) ‑> Verbex -
-
-

Find a line break.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def line_break(self) -> Verbex:
-    """Find a line break.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self.find(SpecialChar.LINEBREAK)
-
-
-
-def maybe(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Possibly find the text / Verbex object.

-

Arguments

-

text – The text / Verbex object to possibly find.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Possibly find the text / Verbex object.
-
-    Arguments:
-        text -- The text / Verbex object to possibly find.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?:{str(text)})?")
-
-
-
-def n_times(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int) ‑> Verbex -
-
-

Find the text or Verbex object n or more times.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def n_times(
+    text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]
+) -> verbex.verbex.Verbex:
+    
+
+            
+ View Source +
279    @re_escape
+280    @beartype
+281    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+282        """`or` is a python keyword so we use `OR` instead.
+283
+284        Arguments:
+285            text -- Text to find or a Verbex object.
+286
+287        Returns:
+288            Modified Verbex object.
+289        """
+290        return self._add("|").find(text)
+
+ +
+ +

or is a python keyword so we use OR instead.

+ +

Arguments: + text -- Text to find or a Verbex object.

+ +

Returns: + Modified Verbex object.

+
+ + + +
+
#   + +
@re_escape
+
@beartype
+ + def + zero_or_more( self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 -) -> Verbex: - """Find the text or Verbex object n or more times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n}}}")
-
-
-
-def n_times_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int) ‑> Verbex -
-
-

Find the text or Verbex object at least n times.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def n_times_or_more(
+    text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]
+) -> verbex.verbex.Verbex:
+    
+
+            
+ View Source +
292    @re_escape
+293    @beartype
+294    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+295        """Find the text or Verbex object zero or more times.
+296
+297        Arguments:
+298            text -- The text / Verbex object to look for.
+299
+300        Returns:
+301            Modified Verbex object.
+302        """
+303        return self._add(f"(?:{str(text)})*")
+
+ +
+ +

Find the text or Verbex object zero or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + + +
+
#   + +
@re_escape
+
@beartype
+ + def + one_or_more( self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 -) -> Verbex: - """Find the text or Verbex object at least n times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n},}}")
-
-
-
-def n_to_m_times(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar], n: int, m: int) ‑> Verbex -
-
-

Find the text or Verbex object between n and m times.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def n_to_m_times(
+    text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]
+) -> verbex.verbex.Verbex:
+    
+
+            
+ View Source +
305    @re_escape
+306    @beartype
+307    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+308        """Find the text or Verbex object one or more times.
+309
+310        Arguments:
+311            text -- The text / Verbex object to look for.
+312
+313        Returns:
+314            Modified Verbex object.
+315        """
+316        return self._add(f"(?:{str(text)})+")
+
+ +
+ +

Find the text or Verbex object one or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + + +
+
#   + +
@re_escape
+
@beartype
+ + def + n_times( self, - text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 - m: int, # noqa: VNE001 -) -> Verbex: - """Find the text or Verbex object between n and m times. - - Arguments: - text -- The text / Verbex object to look for. - - Returns: - Modified Verbex object. - """ - return self._add(f"(?:{str(text)}){{{n},{m}}}")
-
-
-
-def not_any_of(self, text: Union[str, CharClass]) ‑> Verbex -
-
-

Find anything but this group of chars or char class.

-

Arguments

-

text – The characters to not look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def not_any_of(self, text: CharClassOrChars) -> Verbex:
-    """Find anything but this group of chars or char class.
-
-    Arguments:
-        text -- The characters to not look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?:[^{text}])")
-
-
-
-def not_followed_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Match if string is not followed by text.

+ text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
318    @re_escape
+319    @beartype
+320    def n_times(
+321        self,
+322        text: VerbexEscapedCharClassOrSpecial,
+323        n: int,  # noqa: VNE001
+324    ) -> Verbex:
+325        """Find the text or Verbex object n or more times.
+326
+327        Arguments:
+328            text -- The text / Verbex object to look for.
+329
+330        Returns:
+331            Modified Verbex object.
+332        """
+333        return self._add(f"(?:{str(text)}){{{n}}}")
+
+ +
+ +

Find the text or Verbex object n or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + + +
+
#   + +
@re_escape
+
@beartype
+ + def + n_times_or_more( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
335    @re_escape
+336    @beartype
+337    def n_times_or_more(
+338        self,
+339        text: VerbexEscapedCharClassOrSpecial,
+340        n: int,  # noqa: VNE001
+341    ) -> Verbex:
+342        """Find the text or Verbex object at least n times.
+343
+344        Arguments:
+345            text -- The text / Verbex object to look for.
+346
+347        Returns:
+348            Modified Verbex object.
+349        """
+350        return self._add(f"(?:{str(text)}){{{n},}}")
+
+ +
+ +

Find the text or Verbex object at least n times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + n_to_m_times( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int, + m: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
352    @re_escape
+353    @beartype
+354    def n_to_m_times(
+355        self,
+356        text: VerbexEscapedCharClassOrSpecial,
+357        n: int,  # noqa: VNE001
+358        m: int,  # noqa: VNE001
+359    ) -> Verbex:
+360        """Find the text or Verbex object between n and m times.
+361
+362        Arguments:
+363            text -- The text / Verbex object to look for.
+364
+365        Returns:
+366            Modified Verbex object.
+367        """
+368        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+
+ +
+ +

Find the text or Verbex object between n and m times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + maybe( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
370    @re_escape
+371    @beartype
+372    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+373        """Possibly find the text / Verbex object.
+374
+375        Arguments:
+376            text -- The text / Verbex object to possibly find.
+377
+378        Returns:
+379            Modified Verbex object.
+380        """
+381        return self._add(f"(?:{str(text)})?")
+
+ +
+ +

Possibly find the text / Verbex object.

+ +

Arguments: + text -- The text / Verbex object to possibly find.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + find( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
383    @re_escape
+384    @beartype
+385    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+386        """Find the text or Verbex object.
+387
+388        Arguments:
+389            text -- The text / Verbex object to look for.
+390
+391        Returns:
+392            Modified Verbex object.
+393        """
+394        return self._add(str(text))
+
+ +
+ +

Find the text or Verbex object.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + then( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
396    @re_escape
+397    @beartype
+398    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+399        """Synonym for find.
+400
+401        Arguments:
+402            text -- The text / Verbex object to look for.
+403
+404        Returns:
+405            Modified Verbex object.
+406        """
+407        return self.find(text)
+
+ +
+ +

Synonym for find.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + followed_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
409    @re_escape
+410    @beartype
+411    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+412        """Match if string is followed by text.
+413
+414        Positive lookahead
+415
+416        Returns:
+417            Modified Verbex object.
+418        """
+419        return self._add(f"(?={text})")
+
+ +
+ +

Match if string is followed by text.

+ +

Positive lookahead

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + not_followed_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
421    @re_escape
+422    @beartype
+423    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+424        """Match if string is not followed by text.
+425
+426        Negative lookahead
+427
+428        Returns:
+429            Modified Verbex object.
+430        """
+431        return self._add(f"(?!{text})")
+
+ +
+ +

Match if string is not followed by text.

+

Negative lookahead

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Match if string is not followed by text.
-
-    Negative lookahead
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?!{text})")
-
-
-
-def not_preceded_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Match if string is not preceded by text.

-

Negative Lookbehind

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Match if string is not preceded by text.
-
-    Negative Lookbehind
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?<!{text})")
-
-
-
-def number_range(self, start: int, end: int) ‑> Verbex -
-
-

Generate a range of numbers.

-

Arguments

-

start – Start of the range -end – End of the range

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@beartype
-def number_range(self, start: int, end: int) -> Verbex:
-    """Generate a range of numbers.
-
-    Arguments:
-        start -- Start of the range
-        end -- End of the range
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-
-
-
-def one_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Find the text or Verbex object one or more times.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Find the text or Verbex object one or more times.
-
-    Arguments:
-        text -- The text / Verbex object to look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?:{str(text)})+")
-
-
-
-def preceded_by(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Match if string is not preceded by text.

+ +

Returns: + Modified Verbex object.

+
+ + + +
+
#   + +
@re_escape
+
@beartype
+ + def + preceded_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
433    @re_escape
+434    @beartype
+435    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+436        """Match if string is not preceded by text.
+437
+438        Positive lookbehind
+439
+440        Returns:
+441            Modified Verbex object.
+442        """
+443        return self._add(f"(?<={text})")
+
+ +
+ +

Match if string is not preceded by text.

+

Positive lookbehind

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Match if string is not preceded by text.
-
-    Positive lookbehind
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?<={text})")
-
-
-
-def regex(self) ‑> Pattern[str] -
-
-

Get a regular expression object.

-
- -Expand source code - -
def regex(self) -> Pattern[str]:
-    """Get a regular expression object."""
-    return re.compile(
-        str(self),
-        self._modifiers,
-    )
-
-
-
-def search_by_line(self) ‑> Verbex -
-
-

Search each line, ^ and $ match begining and end of line respectively.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def search_by_line(self) -> Verbex:
-    """Search each line, ^ and $ match begining and end of line respectively.
-
-    Returns:
-        Modified Verbex object.
-    """
-    self._modifiers |= re.MULTILINE
-    return self
-
-
-
-def start_of_line(self) ‑> Verbex -
-
-

Find the start of the line.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def start_of_line(self) -> Verbex:
-    """Find the start of the line.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self.find(SpecialChar.START_OF_LINE)
-
-
-
-def tab(self) ‑> Verbex -
-
-

Find a tab.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def tab(self) -> Verbex:
-    """Find a tab.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self.find(SpecialChar.TAB)
-
-
-
-def then(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Synonym for find.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Synonym for find.
-
-    Arguments:
-        text -- The text / Verbex object to look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self.find(text)
-
-
-
-def with_any_case(self) ‑> Verbex -
-
-

Modify Verbex object to be case insensitive.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def with_any_case(self) -> Verbex:
-    """Modify Verbex object to be case insensitive.
-
-    Returns:
-        Modified Verbex object.
-    """
-    self._modifiers |= re.IGNORECASE
-    return self
-
-
-
-def with_ascii(self) ‑> Verbex -
-
-

Match ascii instead of unicode.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def with_ascii(self) -> Verbex:
-    """Match ascii instead of unicode.
-
-    Returns:
-        Modified Verbex object.
-    """
-    self._modifiers |= re.ASCII
-    return self
-
-
-
-def word(self) ‑> Verbex -
-
-

Find a word on word boundary.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
def word(self) -> Verbex:
-    """Find a word on word boundary.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add("(\\b\\w+\\b)")
-
-
-
-def zero_or_more(self, text: Union[ForwardRef('Verbex'), str, CharClassSpecialChar]) ‑> Verbex -
-
-

Find the text or Verbex object zero or more times.

-

Arguments

-

text – The text / Verbex object to look for.

-

Returns

-

Modified Verbex object.

-
- -Expand source code - -
@re_escape
-@beartype
-def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-    """Find the text or Verbex object zero or more times.
-
-    Arguments:
-        text -- The text / Verbex object to look for.
-
-    Returns:
-        Modified Verbex object.
-    """
-    return self._add(f"(?:{str(text)})*")
-
-
-
-
-
-
-
- -
- + + + +
+
#   + +
@re_escape
+
@beartype
+ + def + not_preceded_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
445    @re_escape
+446    @beartype
+447    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+448        """Match if string is not preceded by text.
+449
+450        Negative Lookbehind
+451
+452        Returns:
+453            Modified Verbex object.
+454        """
+455        return self._add(f"(?<!{text})")
+
+ +
+ +

Match if string is not preceded by text.

+ +

Negative Lookbehind

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + any_of( + self, + chargroup: Union[str, verbex.verbex.CharClass] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
459    @re_escape
+460    @beartype
+461    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+462        """Find anything in this group of chars or char class.
+463
+464        Arguments:
+465            text -- The characters to look for.
+466
+467        Returns:
+468            Modified Verbex object.
+469        """
+470        return self._add(f"(?:[{chargroup}])")
+
+ +
+ +

Find anything in this group of chars or char class.

+ +

Arguments: + text -- The characters to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + not_any_of( + self, + text: Union[str, verbex.verbex.CharClass] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
472    @re_escape
+473    @beartype
+474    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+475        """Find anything but this group of chars or char class.
+476
+477        Arguments:
+478            text -- The characters to not look for.
+479
+480        Returns:
+481            Modified Verbex object.
+482        """
+483        return self._add(f"(?:[^{text}])")
+
+ +
+ +

Find anything but this group of chars or char class.

+ +

Arguments: + text -- The characters to not look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+ + def + anything_but( + self, + chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
485    @re_escape
+486    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+487        """Find anything one or more times but this group of chars or char class.
+488
+489        Arguments:
+490            text -- The characters to not look for.
+491
+492        Returns:
+493            Modified Verbex object.
+494        """
+495        return self._add(f"[^{chargroup}]+")
+
+ +
+ +

Find anything one or more times but this group of chars or char class.

+ +

Arguments: + text -- The characters to not look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + start_of_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
499    def start_of_line(self) -> Verbex:
+500        """Find the start of the line.
+501
+502        Returns:
+503            Modified Verbex object.
+504        """
+505        return self.find(SpecialChar.START_OF_LINE)
+
+ +
+ +

Find the start of the line.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + end_of_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
507    def end_of_line(self) -> Verbex:
+508        """Find the end of the line.
+509
+510        Returns:
+511            Modified Verbex object.
+512        """
+513        return self.find(SpecialChar.END_OF_LINE)
+
+ +
+ +

Find the end of the line.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + line_break(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
515    def line_break(self) -> Verbex:
+516        """Find a line break.
+517
+518        Returns:
+519            Modified Verbex object.
+520        """
+521        return self.find(SpecialChar.LINEBREAK)
+
+ +
+ +

Find a line break.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + tab(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
523    def tab(self) -> Verbex:
+524        """Find a tab.
+525
+526        Returns:
+527            Modified Verbex object.
+528        """
+529        return self.find(SpecialChar.TAB)
+
+ +
+ +

Find a tab.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + anything(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
531    def anything(self) -> Verbex:
+532        """Find anything one or more time.
+533
+534        Returns:
+535            Modified Verbex object.
+536        """
+537        return self._add(".+")
+
+ +
+ +

Find anything one or more time.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + as_few(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
539    def as_few(self) -> Verbex:
+540        """Modify previous search to not be greedy.
+541
+542        Returns:
+543            Modified Verbex object.
+544        """
+545        return self._add("?")
+
+ +
+ +

Modify previous search to not be greedy.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@beartype
+ + def + number_range(self, start: int, end: int) -> verbex.verbex.Verbex: +
+ +
+ View Source +
547    @beartype
+548    def number_range(self, start: int, end: int) -> Verbex:
+549        """Generate a range of numbers.
+550
+551        Arguments:
+552            start -- Start of the range
+553            end -- End of the range
+554
+555        Returns:
+556            Modified Verbex object.
+557        """
+558        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+
+ +
+ +

Generate a range of numbers.

+ +

Arguments: + start -- Start of the range + end -- End of the range

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@beartype
+ + def + letter_range( + self, + start: typing.Annotated[str, Is[_string_len_is_1]], + end: typing.Annotated[str, Is[_string_len_is_1]] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
560    @beartype
+561    def letter_range(self, start: Char, end: Char) -> Verbex:
+562        """Generate a range of letters.
+563
+564        Arguments:
+565            start -- Start of the range
+566            end -- End of the range
+567
+568        Returns:
+569            Modified Verbex object.
+570        """
+571        return self._add(f"[{start}-{end}]")
+
+ +
+ +

Generate a range of letters.

+ +

Arguments: + start -- Start of the range + end -- End of the range

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + word(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
573    def word(self) -> Verbex:
+574        """Find a word on word boundary.
+575
+576        Returns:
+577            Modified Verbex object.
+578        """
+579        return self._add("(\\b\\w+\\b)")
+
+ +
+ +

Find a word on word boundary.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + with_any_case(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
583    def with_any_case(self) -> Verbex:
+584        """Modify Verbex object to be case insensitive.
+585
+586        Returns:
+587            Modified Verbex object.
+588        """
+589        self._modifiers |= re.IGNORECASE
+590        return self
+
+ +
+ +

Modify Verbex object to be case insensitive.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + search_by_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
592    def search_by_line(self) -> Verbex:
+593        """Search each line, ^ and $ match begining and end of line respectively.
+594
+595        Returns:
+596            Modified Verbex object.
+597        """
+598        self._modifiers |= re.MULTILINE
+599        return self
+
+ +
+ +

Search each line, ^ and $ match begining and end of line respectively.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + with_ascii(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
601    def with_ascii(self) -> Verbex:
+602        """Match ascii instead of unicode.
+603
+604        Returns:
+605            Modified Verbex object.
+606        """
+607        self._modifiers |= re.ASCII
+608        return self
+
+ +
+ +

Match ascii instead of unicode.

+ +

Returns: + Modified Verbex object.

+
+ + +
+ + \ No newline at end of file From 15b1fca668801031277fc70ce5b146bdbbfff6d9 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sat, 7 May 2022 23:54:05 -0400 Subject: [PATCH 28/90] Set theme jekyll-theme-cayman --- docs/_config.yml | 1 + docs/index.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs/_config.yml create mode 100644 docs/index.md diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..8a582bc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,37 @@ +## Welcome to GitHub Pages + +You can use the [editor on GitHub](https://github.com/rbroderi/Verbex/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. + +Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. + +### Markdown + +Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for + +```markdown +Syntax highlighted code block + +# Header 1 +## Header 2 +### Header 3 + +- Bulleted +- List + +1. Numbered +2. List + +**Bold** and _Italic_ and `Code` text + +[Link](url) and ![Image](src) +``` + +For more details see [Basic writing and formatting syntax](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). + +### Jekyll Themes + +Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/rbroderi/Verbex/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. + +### Support or Contact + +Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out. From 3e71c04f1e4b377998504138a1d36e3f8b31a60a Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sat, 7 May 2022 23:55:04 -0400 Subject: [PATCH 29/90] Delete docs directory --- docs/_config.yml | 1 - docs/index.md | 37 ------------------------------------- 2 files changed, 38 deletions(-) delete mode 100644 docs/_config.yml delete mode 100644 docs/index.md diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index c419263..0000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 8a582bc..0000000 --- a/docs/index.md +++ /dev/null @@ -1,37 +0,0 @@ -## Welcome to GitHub Pages - -You can use the [editor on GitHub](https://github.com/rbroderi/Verbex/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. - -Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. - -### Markdown - -Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for - -```markdown -Syntax highlighted code block - -# Header 1 -## Header 2 -### Header 3 - -- Bulleted -- List - -1. Numbered -2. List - -**Bold** and _Italic_ and `Code` text - -[Link](url) and ![Image](src) -``` - -For more details see [Basic writing and formatting syntax](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). - -### Jekyll Themes - -Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/rbroderi/Verbex/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. - -### Support or Contact - -Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out. From 03f293e45d9e59eb198981f4f3c4f0c11802c6b4 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 23:55:29 -0400 Subject: [PATCH 30/90] add github pages --- builddoc.bat | 2 +- {html => docs}/index.html | 0 {html => docs}/search.js | 0 {html => docs}/verbex/verbex.html | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename {html => docs}/index.html (100%) rename {html => docs}/search.js (100%) rename {html => docs}/verbex/verbex.html (100%) diff --git a/builddoc.bat b/builddoc.bat index e2920aa..8bc79f5 100644 --- a/builddoc.bat +++ b/builddoc.bat @@ -1,3 +1,3 @@ pushd "%~dp0" -pdoc verbex/verbex -o html +pdoc verbex/verbex -o docs pause \ No newline at end of file diff --git a/html/index.html b/docs/index.html similarity index 100% rename from html/index.html rename to docs/index.html diff --git a/html/search.js b/docs/search.js similarity index 100% rename from html/search.js rename to docs/search.js diff --git a/html/verbex/verbex.html b/docs/verbex/verbex.html similarity index 100% rename from html/verbex/verbex.html rename to docs/verbex/verbex.html From b7d2533ec68d12d637d495d0994f782230746836 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 23:58:28 -0400 Subject: [PATCH 31/90] update readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 94abab5..27acb3e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,9 @@ pip install Verbex from verbex import Verbex verbex = Verbex() ``` + +## Documentation +[API]https://rbroderi.github.io/Verbex/verbex/verbex.html ## Examples ### Testing if we have a valid URL From 4bdd63aae010bb742a3b3876b59eb50f23e71ea7 Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 May 2022 23:59:10 -0400 Subject: [PATCH 32/90] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27acb3e..e390a94 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ verbex = Verbex() ``` ## Documentation -[API]https://rbroderi.github.io/Verbex/verbex/verbex.html +[API](https://rbroderi.github.io/Verbex/verbex/verbex.html) ## Examples ### Testing if we have a valid URL From 66778e450b191717af03e2f8654328b09323d24e Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 09:16:17 -0400 Subject: [PATCH 33/90] remove travis.yaml dependency --- .github/workflows/main.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0383dc3..caf8baa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,21 +18,22 @@ jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on - runs-on: ubuntu-latest - + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-versions: ['3.6','3.7','3.8','3.9','3.10', '3.11'] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 - - name: Install xmllint - run: pip install typing-extensions beartype - - name: Run .travis.yml build script - uses: ktomk/run-travis-yml@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 with: - file: .travis.yml - steps: | - install - script - allow-failure: false - env: - TRAVIS_PHP_VERSION: ${{ matrix.php-versions }} + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install typing-extensions beartype + - name: Run tests + run: python setup.py test From c9f4e5165c060f6f1f3ce3d544ec8641afcba503 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 09:17:12 -0400 Subject: [PATCH 34/90] remove travis.yaml dependency --- .github/workflows/main.yml | 4 ++-- .travis.yml | 11 ----------- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index caf8baa..1c901b4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install typing-extensions beartype + python -m pip install --upgrade pip + pip install typing-extensions beartype - name: Run tests run: python setup.py test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8622eeb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: python -python: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "3.10" - - "3.11" - -# command to run tests -script: python setup.py test From 7fd6e73ce2d3aedd4c4118759bd4945f608b69a1 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 10:16:58 -0400 Subject: [PATCH 35/90] switch config around, using setup.cfg and, tox --- .gitattributes | 50 ++++++++ .github/workflows/main.yml | 8 +- .gitignore | 19 ++- .pre-commit-config.yaml | 213 ++++++++++++++++++++++++++++++++ .yamllint | 6 + MANIFEST.IN | 2 +- builddoc.bat | 2 +- dist/Verbex-1.1.0.win-amd64.zip | Bin 0 -> 12788 bytes docs/search.js | 2 +- docs/verbex/verbex.html | 108 ++++++++-------- pyproject.toml | 30 +++++ requirements.in | 1 + requirements.txt | 8 ++ requirements_dev.in | 4 + requirements_dev.txt | 70 +++++++++++ setup.cfg | 24 ++++ setup.py | 38 +----- setup.py.old | 38 ++++++ verbex/verbex.py | 4 +- 19 files changed, 526 insertions(+), 101 deletions(-) create mode 100644 .gitattributes create mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint create mode 100644 dist/Verbex-1.1.0.win-amd64.zip create mode 100644 pyproject.toml create mode 100644 requirements.in create mode 100644 requirements.txt create mode 100644 requirements_dev.in create mode 100644 requirements_dev.txt create mode 100644 setup.cfg create mode 100644 setup.py.old diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..72c1b76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,50 @@ +# .gitattributes snippet to force users to use same line endings for project. +# +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# +# The above will handle all files NOT found below +# https://help.github.com/articles/dealing-with-line-endings/ +# https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes + + + +# These files are text and should be normalized (Convert crlf => lf) +*.php text +*.css text +*.js text +*.json text +*.htm text +*.html text +*.xml text +*.txt text +*.ini text +*.inc text +*.pl text +*.rb text +*.py text +*.scm text +*.sql text +.htaccess text +*.sh text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.pyc binary diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c901b4..7a3605c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,12 +3,12 @@ name: CI # Controls when the workflow will run -on: +on: # yamllint disable-line rule:truthy # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-versions: ['3.6','3.7','3.8','3.9','3.10', '3.11'] + python-versions: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/.gitignore b/.gitignore index a0dcaf0..af4b16f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,20 @@ __pycache__/ build/ -.mypy_cache/ +.vscode/ +.tox/ +eggs/ +.eggs/ *.egg-info/ -.vscode/ \ No newline at end of file +~* +.mypy_cache/ +.pspp +.cache/ +.viminfo +.idea/ +desktop.ini +.gitconfig +.recommenders +.metadata/ +.venv/ +venv/ +__pycache__/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ebae4e6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,213 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +minimum_pre_commit_version: 1.21.0 +repos: + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes + # - repo: local + # hooks: + # - id: verify_ascii + # name: "Check for non ascii chars in file names" + # entry: "./Src/Bash/verifyAscii.sh" + # language: script + # pass_filenames: false + # require_serial: true + # always_run: true + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + types: [file, text] + - id: end-of-file-fixer + types: [file, text] + - id: check-case-conflict + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: check-merge-conflict + name: "Check for merge conflicts" + - id: check-yaml + name: "Yaml: Check files" + types: [file, yaml] + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.26.3 # or higher tag + hooks: + - id: yamllint + name: "Yaml: Linting files" + args: [--format, parsable, --strict] + types: [file, yaml] + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.1.13 + hooks: + - id: remove-tabs + name: "Python: Convert Tabs to 4 spaces" + args: ['--whitespaces-count', '4'] # defaults to: 4 + types: [file, python] +# seems to freeze with more than one file ?! + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + name: "Python: Formating files" + args: [--line-length=88, --preview, --safe] + types: [file, python] + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + name: "Python: Formating code in docstrings" + additional_dependencies: [black==20.8b1] + types: [file, python] + - repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + name: "Python: Sorting imports" + types: [file, python] + args: + - "--multi-line=3" # makes this compatible with add-trailing-comma + - "--trailing-comma" # makes this compatible with add-trailing-comma + - "--profile" + - "black" + # - repo: local + # hooks: + # - id: python_file_name_check + # name: "Python: File name check" + # entry: "./Src/Bash/pythonCheckFilenames.sh" + # language: script + # pass_filenames: true + # types: [file, python] + # verbose: false + - repo: https://github.com/pycqa/flake8 + rev: '4.0.1' # old 4.0.1 seem to freeze? + hooks: + - id: flake8 + name: "Python: Linting files (flake8)" + args: # arguments to configure flake8 + # making isort line length compatible with black + - "--max-line-length=88" + - "--max-complexity=18" + # allowing these errors now that in the past we ignored. + # D100 Missing docstring in public module + # D103 Missing docstring in public function + # D104 Missing docstring in public package + # D105 Missing docstring in magic method + # D107 Missing docstring in __init__ + # D200 One-line docstring should fit on one line with quotes + # D205 1 blank line required between summary line and description + # D400 First line should end with a period + # D401 First line should be in imperative mood + # D403 First word of the first line should be properly capitalized + # these are errors that will be ignored by flake8 + # VNE002 variable name 'XXX' should be clarified + # W503 see https://www.flake8rules.com/rules/W503.html no longer best practice + # - "--ignore=VNE002,W503" + # removed cohesion as it was causing issues with enum type classes + # E203 spaces around ':' ignoring per https://github.com/psf/black/issues/315 + # PD005 and PD011 falsely flag on other add or values methods + - "--ignore=W503,E203, PD005, PD011" + # when checking with wemake - "--ignore=W503,E203, PD005, PD011, WPS226, WPS112, WPS204, Q000, WPS421, WPS305, WPS237, WPS529, E800, C812, WPS110, WPS360, WPS323" + additional_dependencies: + - flake8-blind-except + - flake8-assert-msg + - flake8-builtins + - flake8-docstrings +# - flake8-requirements # removed as there was issues with requirements creation tool + - flake8-implicit-str-concat + - flake8-mock + - flake8-variables-names + - pep8-naming + - flake8-bugbear + - flake8-executable + - flake8-raise + - flake8-pytest + - flake8-use-pathlib + - flake8-string-format + - flake8-colors + - flake8-tuple + - pandas-vet + # - wemake-python-styleguide + exclude: "setup[.]py|conf[.]py|__init__[.]py" + types: [file, python] + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.2.3 + hooks: + - id: add-trailing-comma + name: "Python: Add trailing comma" + args: [--py36-plus] + types: [file, python] + # - repo: local + # hooks: + # - id: python_package_check + # name: "Python: Checking Package Structure" + # entry: "./Src/Bash/pythonCheckPackage.sh" + # language: script + # pass_filenames: false + # verbose: false + # require_serial: true + # types: [file, python] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.950' + hooks: + - id: mypy + name: "Python: Checking variable types" + args: [--ignore-missing-imports, --allow-redefinition] + exclude: "setup[.]py|conf[.]py" + additional_dependencies: + - pydantic + - types-all + - pandas-stubs + types: [file, python] + - repo: https://github.com/PyCQA/bandit + rev: '1.7.4' + hooks: + - id: bandit + name: "Python: Checking for potential security issues (bandit)" + args: + - "--skip=B404,B506,B607,B603,B701,B101,B602" + - repo: https://github.com/jazzband/pip-tools + rev: 6.6.0 + hooks: + - id: pip-compile + name: "Python: Compile any requirements.in to requirements.txt" + args: [--quiet, --no-allow-unsafe, requirements.in] + files: requirements[.]in + - id: pip-compile + name: "Python: Compile any requirements_dev.in to requirements_dev.txt" + args: [--quiet, --no-allow-unsafe, requirements_dev.in] + files: requirements_dev[.]in + - repo: https://github.com/Lucas-C/pre-commit-hooks-safety + rev: v1.2.4 + hooks: + - id: python-safety-dependencies-check + name: "Python: Checking requirements.txt files for vulnerablitites" + always_run: true + files: requirements.txt + args: [requirements.txt] + - id: python-safety-dependencies-check + name: "Python: Checking requirements_dev.txt files for vulnerablitites" + always_run: true + files: requirements_dev.txt + args: [requirements_dev.txt] + - repo: local + hooks: + - id: remove-en-dashes + name: Remove the EXTREMELY confusing unicode character U+2013 + language: system + entry: perl -pi* -e 's/\xe2\x80\x93/-/g && ($t = 1) && print STDERR $_; END{{exit $t}}' + types: [file] + types_or: [python, powershell, lua, jinja] + - repo: https://github.com/sirosen/texthooks + rev: 0.3.1 + hooks: + - id: fix-smartquotes + types: [file] + types_or: [python, powershell, lua, jinja] + - id: fix-ligatures + types: [file] + types_or: [python, powershell, lua, jinja] + - id: forbid-bidi-controls + types: [file] + types_or: [python, powershell, lua, jinja] diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..afbda05 --- /dev/null +++ b/.yamllint @@ -0,0 +1,6 @@ +extends: default +rules: + comments-indentation: disable + document-start: disable + new-lines: disable + line-length: disable diff --git a/MANIFEST.IN b/MANIFEST.IN index e0d237d..9714462 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,3 +1,3 @@ include verbex/py.typed include LICENSE.TXT -include GPLv3_LICENSE.txt \ No newline at end of file +include GPLv3_LICENSE.txt diff --git a/builddoc.bat b/builddoc.bat index 8bc79f5..c5f576f 100644 --- a/builddoc.bat +++ b/builddoc.bat @@ -1,3 +1,3 @@ pushd "%~dp0" pdoc verbex/verbex -o docs -pause \ No newline at end of file +pause diff --git a/dist/Verbex-1.1.0.win-amd64.zip b/dist/Verbex-1.1.0.win-amd64.zip new file mode 100644 index 0000000000000000000000000000000000000000..5c11901347bb1332b4914205efd0fcb1d8ef734f GIT binary patch literal 12788 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 P = ~P - - + +
@@ -907,7 +907,7 @@

#   - + HasIter(*args, **kwargs)
@@ -944,7 +944,7 @@

- +

@@ -982,7 +982,7 @@

#   - + HasItems(*args, **kwargs)
@@ -1019,13 +1019,13 @@

- +

#   - + def items(self) -> tuple[str, typing.Any]:
@@ -1056,7 +1056,7 @@

#   - + class EscapedText(builtins.str):
@@ -1094,7 +1094,7 @@

#   - + EscapedText(value: str)
@@ -1184,7 +1184,7 @@
Inherited Members
#   - + def re_escape( func: collections.abc.Callable[~P, ~R] @@ -1241,7 +1241,7 @@
Inherited Members
#   - + class CharClass(enum.Enum):
@@ -1286,8 +1286,8 @@
Inherited Members
DIGIT = <CharClass.DIGIT: '\\d'>
- - + +
@@ -1296,8 +1296,8 @@
Inherited Members
LETTER = <CharClass.LETTER: '\\w'>
- - + +

@@ -1306,8 +1306,8 @@
Inherited Members
UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'>
- - + +
@@ -1316,8 +1316,8 @@
Inherited Members
LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'>
- - + +
@@ -1326,8 +1326,8 @@
Inherited Members
WHITESPACE = <CharClass.WHITESPACE: '\\s'>
- - + +
@@ -1336,8 +1336,8 @@
Inherited Members
TAB = <CharClass.TAB: '\\t'>
- - + +
@@ -1355,7 +1355,7 @@
Inherited Members
#   - + class SpecialChar(enum.Enum):
@@ -1399,8 +1399,8 @@
Inherited Members
LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'>
- - + +
@@ -1409,8 +1409,8 @@
Inherited Members
START_OF_LINE = <SpecialChar.START_OF_LINE: '^'>
- - + +
@@ -1419,8 +1419,8 @@
Inherited Members
END_OF_LINE = <SpecialChar.END_OF_LINE: '$'>
- - + +
@@ -1429,8 +1429,8 @@
Inherited Members
TAB = <SpecialChar.TAB: '\t'>
- - + +
@@ -1450,8 +1450,8 @@
Inherited Members
CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass]
- - + +
@@ -1460,8 +1460,8 @@
Inherited Members
EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] - - + +
@@ -1470,15 +1470,15 @@
Inherited Members
VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] - - + +
#   - + class Verbex:
@@ -1968,8 +1968,8 @@
Inherited Members
EMPTY_REGEX_FLAG = - - + +
@@ -1978,7 +1978,7 @@
Inherited Members
modifiers: re.RegexFlag
- +

Return the modifiers for this Verbex object.

Returns: @@ -1990,7 +1990,7 @@

Inherited Members
#   - + def regex(self) -> Pattern[str]:
@@ -2761,7 +2761,7 @@
Inherited Members
#   - + def start_of_line(self) -> verbex.verbex.Verbex:
@@ -2790,7 +2790,7 @@
Inherited Members
#   - + def end_of_line(self) -> verbex.verbex.Verbex:
@@ -2819,7 +2819,7 @@
Inherited Members
#   - + def line_break(self) -> verbex.verbex.Verbex:
@@ -2848,7 +2848,7 @@
Inherited Members
#   - + def tab(self) -> verbex.verbex.Verbex:
@@ -2877,7 +2877,7 @@
Inherited Members
#   - + def anything(self) -> verbex.verbex.Verbex:
@@ -2906,7 +2906,7 @@
Inherited Members
#   - + def as_few(self) -> verbex.verbex.Verbex:
@@ -3017,7 +3017,7 @@
Inherited Members
#   - + def word(self) -> verbex.verbex.Verbex:
@@ -3046,7 +3046,7 @@
Inherited Members
#   - + def with_any_case(self) -> verbex.verbex.Verbex:
@@ -3076,7 +3076,7 @@
Inherited Members
#   - + def search_by_line(self) -> verbex.verbex.Verbex:
@@ -3106,7 +3106,7 @@
Inherited Members
#   - + def with_ascii(self) -> verbex.verbex.Verbex:
@@ -3136,4 +3136,4 @@
Inherited Members
- \ No newline at end of file + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7fb5350 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = [ "setuptools >= 35.0.2", "wheel >= 0.29.0"] +build-backend = "setuptools.build_meta" + +[tool.tox] +legacy_tox_ini = """ +[tox] +envlist = py36, py37, py38, py39, py310, py311 +isolated_build = true + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +deps = -r{toxinidir}/requirements_dev.txt +commands = python -m unittest discover -v +""" + +[tool.isort] +profile = "black" +sections = ["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"] + +[tool.mypy] +plugins = "pydantic.mypy" +follow_imports = "silent" +warn_redundant_casts = true +warn_unused_ignores = false +disallow_any_generics = true +check_untyped_defs = true +no_implicit_reexport = true +disallow_untyped_defs = true diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..e370cca --- /dev/null +++ b/requirements.in @@ -0,0 +1 @@ +beartype diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fd32cff --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile requirements.in +# +beartype==0.10.4 + # via -r requirements.in diff --git a/requirements_dev.in b/requirements_dev.in new file mode 100644 index 0000000..29b6f24 --- /dev/null +++ b/requirements_dev.in @@ -0,0 +1,4 @@ +tox +mypy>=0.950 +black +pre-commit diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..520c051 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,70 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile requirements_dev.in +# +black==22.3.0 + # via -r requirements_dev.in +cfgv==3.3.1 + # via pre-commit +click==8.1.3 + # via black +colorama==0.4.4 + # via + # click + # tox +distlib==0.3.4 + # via virtualenv +filelock==3.6.0 + # via + # tox + # virtualenv +identify==2.5.0 + # via pre-commit +mypy==0.950 + # via -r requirements_dev.in +mypy-extensions==0.4.3 + # via + # black + # mypy +nodeenv==1.6.0 + # via pre-commit +packaging==21.3 + # via tox +pathspec==0.9.0 + # via black +platformdirs==2.5.2 + # via + # black + # virtualenv +pluggy==1.0.0 + # via tox +pre-commit==2.19.0 + # via -r requirements_dev.in +py==1.11.0 + # via tox +pyparsing==3.0.8 + # via packaging +pyyaml==6.0 + # via pre-commit +six==1.16.0 + # via + # tox + # virtualenv +toml==0.10.2 + # via + # pre-commit + # tox +tomli==2.0.1 + # via + # black + # mypy +tox==3.25.0 + # via -r requirements_dev.in +typing-extensions==4.2.0 + # via mypy +virtualenv==20.14.1 + # via + # pre-commit + # tox diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..62ab083 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,24 @@ +[metadata] +name = Verbex +version = 1.1.0 +author = Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer Raghuram, Kharms, Richard Broderick +license = GPLv3 +description = Make difficult regular expressions easy! Python fork based on of the awesome VerbalExpressions repo - https://github.com/jehna/VerbalExpressions +url = https://github.com/rbroderi/Verbex +long_description = file: README.rst +long_description_content_type = text/markdown +classifiers = + License :: OSI Approved :: GNU General Public License v3 (GPLv3) + Programming Language :: Python + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Topic :: Software Development :: Libraries + Topic :: Text Processing + +[options] +packages = verbex +include_package_data = True diff --git a/setup.py b/setup.py index 34d1196..7f1a176 100755 --- a/setup.py +++ b/setup.py @@ -1,38 +1,4 @@ -from pathlib import Path - from setuptools import setup -SCRIPT_ROOT = Path(__file__).parent -long_description = (SCRIPT_ROOT / "README.md").read_text() - -setup( - name="Verbex", - version="1.0.3", - description=( - "Make difficult regular expressions easy! Python fork based on of the awesome" - " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" - ), - long_description=long_description, - long_description_content_type="text/markdown", - author=( - "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" - " Raghuram, Kharms, Richard Broderick" - ), - license="GPLv3", - url="https://github.com/rbroderi/Verbex", - test_suite="tests", - packages=["verbex"], - classifiers=[ - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Software Development :: Libraries", - "Topic :: Text Processing", - ], - include_package_data=True, -) +if __name__ == "__main__": + setup() diff --git a/setup.py.old b/setup.py.old new file mode 100644 index 0000000..34d1196 --- /dev/null +++ b/setup.py.old @@ -0,0 +1,38 @@ +from pathlib import Path + +from setuptools import setup + +SCRIPT_ROOT = Path(__file__).parent +long_description = (SCRIPT_ROOT / "README.md").read_text() + +setup( + name="Verbex", + version="1.0.3", + description=( + "Make difficult regular expressions easy! Python fork based on of the awesome" + " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" + ), + long_description=long_description, + long_description_content_type="text/markdown", + author=( + "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" + " Raghuram, Kharms, Richard Broderick" + ), + license="GPLv3", + url="https://github.com/rbroderi/Verbex", + test_suite="tests", + packages=["verbex"], + classifiers=[ + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Libraries", + "Topic :: Text Processing", + ], + include_package_data=True, +) diff --git a/verbex/verbex.py b/verbex/verbex.py index 1287218..6f5f1e6 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -11,8 +11,8 @@ ParamSpec, TypeAlias, ) -except (ModuleNotFoundError, ImportError): - from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 +except ImportError: + from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 # noqa E501 from typing import Pattern, Protocol, TypeVar From ab9b9ac842d12d3fc6298a3e737bd15ae2c1fbe1 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:10:43 -0400 Subject: [PATCH 36/90] adde pre-commit hooks and pyproject.toml --- .pre-commit-config.yaml | 46 ++++++++++---------------- check_names.py | 71 +++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + setup.cfg | 2 +- 4 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 check_names.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ebae4e6..4fbd903 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,15 +6,6 @@ repos: hooks: - id: check-hooks-apply - id: check-useless-excludes - # - repo: local - # hooks: - # - id: verify_ascii - # name: "Check for non ascii chars in file names" - # entry: "./Src/Bash/verifyAscii.sh" - # language: script - # pass_filenames: false - # require_serial: true - # always_run: true - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: @@ -31,6 +22,13 @@ repos: - id: check-yaml name: "Yaml: Check files" types: [file, yaml] + - id: check-toml + name: "TOML: check toml syntax" + types: [file, toml] + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.14.3 + hooks: + - id: check-github-workflows - repo: https://github.com/adrienverge/yamllint.git rev: v1.26.3 # or higher tag hooks: @@ -45,7 +43,6 @@ repos: name: "Python: Convert Tabs to 4 spaces" args: ['--whitespaces-count', '4'] # defaults to: 4 types: [file, python] -# seems to freeze with more than one file ?! - repo: https://github.com/psf/black rev: 22.3.0 hooks: @@ -71,15 +68,15 @@ repos: - "--trailing-comma" # makes this compatible with add-trailing-comma - "--profile" - "black" - # - repo: local - # hooks: - # - id: python_file_name_check - # name: "Python: File name check" - # entry: "./Src/Bash/pythonCheckFilenames.sh" - # language: script - # pass_filenames: true - # types: [file, python] - # verbose: false + - repo: local + hooks: + - id: python_file_name_check + name: "Python: File name check" + entry: "python ./check_names.py" + language: python + pass_filenames: true + types: [file, python] + verbose: false - repo: https://github.com/pycqa/flake8 rev: '4.0.1' # old 4.0.1 seem to freeze? hooks: @@ -114,7 +111,6 @@ repos: - flake8-assert-msg - flake8-builtins - flake8-docstrings -# - flake8-requirements # removed as there was issues with requirements creation tool - flake8-implicit-str-concat - flake8-mock - flake8-variables-names @@ -138,16 +134,6 @@ repos: name: "Python: Add trailing comma" args: [--py36-plus] types: [file, python] - # - repo: local - # hooks: - # - id: python_package_check - # name: "Python: Checking Package Structure" - # entry: "./Src/Bash/pythonCheckPackage.sh" - # language: script - # pass_filenames: false - # verbose: false - # require_serial: true - # types: [file, python] - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v0.950' hooks: diff --git a/check_names.py b/check_names.py new file mode 100644 index 0000000..b6d6eeb --- /dev/null +++ b/check_names.py @@ -0,0 +1,71 @@ +"""Checks module and package names for pep8 compliance.""" +import argparse +import re +from enum import IntEnum +from pathlib import Path + +try: + from exit_codes.exit_codes import ExitCode +except ImportError: + + class ExitCode(IntEnum): # type: ignore + """Redefine in case ExitCode is not installed.""" + + OS_FILE = 1 + DATA_ERR = 2 + OK = 0 + + +SHORT_NAME_LIMIT = 30 + + +def main() -> int: + """Check the file.""" + parser = argparse.ArgumentParser() + parser.add_argument("files", nargs="+") + args = parser.parse_args() + for file_to_check in args.files: + # verify file exists + file_path = Path(file_to_check) + if not file_path.exists(): + print("ERROR: the file doesn't exist") + return ExitCode.OS_FILE + module_name = file_path.stem + package_name = file_path.parent.name + # check length for module and package name + if len(module_name) > SHORT_NAME_LIMIT: + print(f"ERROR: '{module_name}' is longer than {SHORT_NAME_LIMIT}") + return ExitCode.DATA_ERR + if len(package_name) > SHORT_NAME_LIMIT: + print(f"ERROR: '{package_name}' is longer than {SHORT_NAME_LIMIT}") + return ExitCode.DATA_ERR + # check module name + if not re.fullmatch("[A-Za-z_]+", module_name): + if re.fullmatch("[A-Za-z0-9_]+", module_name): + print( + f"WARNING: '{module_name}' has numbers - allowing but note this is" + " not 'strictly' to pep 8 best practices", + ) + else: + print(f"ERROR: '{module_name}' is not all lowercase with underscores") + return ExitCode.DATA_ERR + # check package if exists + if package_name.strip() != "": + # check package name + if not re.fullmatch("[A-Za-z]+", package_name): + if re.fullmatch("[A-Za-z0-9]+", package_name): + print( + f"WARNING: '{package_name}' has numbers - allowing but note" + " this is not 'strictly' to pep 8 best practices", + ) + else: + print( + f"ERROR: '{package_name}' is not all lowercase with no" + " underscores", + ) + return ExitCode.DATA_ERR + return ExitCode.OK + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pyproject.toml b/pyproject.toml index 7fb5350..ef77986 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ legacy_tox_ini = """ [tox] envlist = py36, py37, py38, py39, py310, py311 isolated_build = true +skip_missing_interpreters = true [testenv] setenv = diff --git a/setup.cfg b/setup.cfg index 62ab083..1d9e5df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ author = Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, S license = GPLv3 description = Make difficult regular expressions easy! Python fork based on of the awesome VerbalExpressions repo - https://github.com/jehna/VerbalExpressions url = https://github.com/rbroderi/Verbex -long_description = file: README.rst +long_description = file: README.md long_description_content_type = text/markdown classifiers = License :: OSI Approved :: GNU General Public License v3 (GPLv3) From 27383e408747b331c56ba78f8a2cf1ab56c08d41 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:16:19 -0400 Subject: [PATCH 37/90] adde pre-commit hooks and pyproject.toml --- .github/workflows/main.yml | 2 +- setup.cfg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a3605c..dee2a75 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,4 +36,4 @@ jobs: python -m pip install --upgrade pip pip install typing-extensions beartype - name: Run tests - run: python setup.py test + run: tox diff --git a/setup.cfg b/setup.cfg index 1d9e5df..0071b46 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,7 @@ classifiers = [options] packages = verbex include_package_data = True + +[options.extras_require] +testing = + tox From 0befb3182f0619c5dbe0286144cfd9ccc78fe0ff Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:17:37 -0400 Subject: [PATCH 38/90] fix gha --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dee2a75..f509aed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,6 +34,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install typing-extensions beartype + pip install typing-extensions beartype tox - name: Run tests run: tox From aa1811bc650f220546e65303b1d9395d567cc7c8 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:25:40 -0400 Subject: [PATCH 39/90] fix gha --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f509aed..50adf0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,12 +28,12 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install typing-extensions beartype tox + pip install typing-extensions beartype tox tox-gh-actions - name: Run tests run: tox From a971431176b5127ee3113d5d79379e07af748440 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:27:30 -0400 Subject: [PATCH 40/90] fix gha --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50adf0d..5d6456c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,9 +31,11 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} + - name: Display Python version + run: python --version - name: Install dependencies run: | python -m pip install --upgrade pip pip install typing-extensions beartype tox tox-gh-actions - name: Run tests - run: tox + run: tox -v From 3e533665c03e1fd475f06dff2cdeab532c621b8e Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:33:36 -0400 Subject: [PATCH 41/90] fix gha --- .github/workflows/main.yml | 2 +- pyproject.toml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5d6456c..ccf5173 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-versions: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/pyproject.toml b/pyproject.toml index ef77986..450c78d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,14 @@ legacy_tox_ini = """ [tox] envlist = py36, py37, py38, py39, py310, py311 isolated_build = true -skip_missing_interpreters = true + +[gh-actions] +python = + 3.7: py37 + 3.8: py38 + 3.9: py39 + 3.10: py310 + 3.11: py311 [testenv] setenv = From c3ee46dd7b1729dedd915d69f164473f1f74fc16 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 11:37:14 -0400 Subject: [PATCH 42/90] fix gha --- .github/workflows/main.yml | 2 +- pyproject.toml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ccf5173..c37f0b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/pyproject.toml b/pyproject.toml index 450c78d..4c277e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,6 @@ python = 3.8: py38 3.9: py39 3.10: py310 - 3.11: py311 - [testenv] setenv = PYTHONPATH = {toxinidir} From 7d6f3e7c140982c97f037744a70ac3ba4a88eb38 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:11:20 -0400 Subject: [PATCH 43/90] use poseur for older python versions --- ...ive-20220508115341-c1a945c08ab1170c.tar.gz | Bin 0 -> 3764 bytes ...ive-20220508115432-8f4db45c4efcdff6.tar.gz | Bin 0 -> 3767 bytes ...ive-20220508115930-3a2864bd92a8e920.tar.gz | Bin 0 -> 3766 bytes pyproject.toml | 1 + verbex/verbex.py | 22 +++++++++++++++++- 5 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 archive/archive-20220508115341-c1a945c08ab1170c.tar.gz create mode 100644 archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz create mode 100644 archive/archive-20220508115930-3a2864bd92a8e920.tar.gz diff --git a/archive/archive-20220508115341-c1a945c08ab1170c.tar.gz b/archive/archive-20220508115341-c1a945c08ab1170c.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2767ac9201f05820b1eea51e22cbe091f2066617 GIT binary patch literal 3764 zcmV;l4omSLiwFpX=XYWP|6y`tXlZt3Eiy1NGB7nTI59CbGc++RV=-YlG&N%|IALNj zF*h({E_7jX0PS4ecH1@*&b7|zJD_qe#6+RxKc&&eZCyu6e45xkvYKv><+2b7S&S)? z1xedl#}BX%vM;tzvNHfkf|O**S<6jR^TL+E0WkB;FF;V{s=4~ZA-#OVsKfZ9rCbep z&HCSHY-pdceXX(HXg(sBkM7}0qL^}+{?Xs_THhq2*d4L$wdU4(W9|9ox9jG!_07to zhwE=~*-c9OgTDQg*@KOzn_CU%DW#hmPy3CnEqklkd_EXFH^cG&?f=bYbB6zKG&i4Z zJzCpb-`IM#)!5wF0RP`;u0MN38V~-z^s8E}zGgn-G-iae)5N2ku*;CMC~^ZoA_E?b z2=xi0k;^z4cnR}ka?W_4dSnprkqIMI1eIkCk~rbavWPnh10F*^KZt3J163-jjWbEj z{A85Uwc)dD9VEUT2Z0x*w=Oshp>xc~tqOrlP{rfW^-oih8X+s>#na+N+yOZp$HTxU z|NZxW$cFjcY={By+L3%Qr=B$(Qcg!lA+z(Q?l@#So=YRyWK`It&BB=MiD}!M2YiAj zi(SUdm(VHp@?o@j^ZAf@=*wEfJ@o_5lq?$mFC_PH!w>$Tq`lQxt5l}oa7g2raUaI! zK^)kDR~K`?r(BWu8Ra-t+9CU1VxdTFPC>gsEfF0{ly<4-(Z0vzx0kLR%TIeT+E~El zx3_Mj+7CiB59-Np-6TxA{_I1${AkmZnkPOcE6W}-`%F$_l(;>oUMApPG{J18;;;d+ zqL`z$Jmy<&WUbX=&|8ZH{SVBJ4f6Cm(g!1y+T{$4+9z)0`VcC7n+f7NfgudAaEHJk zqRAlJ+XPfo*&TutZRgrP2GTk(b6lSGfi$y*TJ^AMkW|(0_uCCnb_XbwCTrEs z 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 ex)MPyhg@H6LC8 literal 0 HcmV?d00001 diff --git a/archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz b/archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e1e7f930d88b0c726493a97a750e8de04448c445 GIT binary patch literal 3767 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 literal 0 HcmV?d00001 diff --git a/archive/archive-20220508115930-3a2864bd92a8e920.tar.gz b/archive/archive-20220508115930-3a2864bd92a8e920.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b60bea7a5240ba9b3103719c129b080a58387bce GIT binary patch literal 3766 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 literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index 4c277e9..04d0fc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ isolated_build = true [gh-actions] python = + 3.6: py36 3.7: py37 3.8: py38 3.9: py39 diff --git a/verbex/verbex.py b/verbex/verbex.py index 6f5f1e6..520a0db 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -173,6 +173,26 @@ def __str__(self) -> str: VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] +def _poseur_decorator(*poseur: Any) -> Any: + """Positional-only arguments runtime checker.""" + import functools + + def caller(func: Callable[P, R]) -> Callable[P, R]: # type: ignore + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + poseur_args = set(poseur).intersection(kwargs) # type: ignore + if poseur_args: + raise TypeError( + "%s() got some positional-only arguments passed as keyword" + " arguments: %r" % (func.__name__, ", ".join(poseur_args)), + ) + return func(*args, **kwargs) # type: ignore + + return wrapper + + return caller + + class Verbex: """ VerbalExpressions class. @@ -248,9 +268,9 @@ def _capture_group_without_name( @re_escape @beartype + @_poseur_decorator("self") def capture_group( self, - /, name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, text: Optional[VerbexEscapedCharClassOrSpecial] = None, ) -> Verbex: From b576312dd3166c236fd7045f5f4cdc64a989a3e1 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:15:53 -0400 Subject: [PATCH 44/90] use poseur for older python versions --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 +- requirements_test.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 requirements_test.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4fbd903..bbf2403 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: hooks: - id: blacken-docs name: "Python: Formating code in docstrings" - additional_dependencies: [black==20.8b1] + additional_dependencies: [black==22.3] types: [file, python] - repo: https://github.com/pycqa/isort rev: 5.10.1 diff --git a/pyproject.toml b/pyproject.toml index 04d0fc5..584c358 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ python = [testenv] setenv = PYTHONPATH = {toxinidir} -deps = -r{toxinidir}/requirements_dev.txt +deps = -r{toxinidir}/requirements_test.txt commands = python -m unittest discover -v """ diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..b654c22 --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1 @@ +tox \ No newline at end of file From fed69335277973f0fb622df46a3cec9b4c40b629 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:18:07 -0400 Subject: [PATCH 45/90] use poseur for older python versions --- .github/workflows/main.yml | 2 +- pyproject.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c37f0b3..f90a665 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python-version: ['3.7', '3.8', '3.9', '3.10'] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it diff --git a/pyproject.toml b/pyproject.toml index 584c358..9dd6e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,12 +5,11 @@ build-backend = "setuptools.build_meta" [tool.tox] legacy_tox_ini = """ [tox] -envlist = py36, py37, py38, py39, py310, py311 +envlist = py37, py38, py39, py310, py311 isolated_build = true [gh-actions] python = - 3.6: py36 3.7: py37 3.8: py38 3.9: py39 From d28af7a819ea885241df665482d375a584788184 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:19:55 -0400 Subject: [PATCH 46/90] use beartype protocol instead --- verbex/verbex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verbex/verbex.py b/verbex/verbex.py index 520a0db..a1ca65a 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -14,7 +14,7 @@ except ImportError: from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 # noqa E501 -from typing import Pattern, Protocol, TypeVar +from typing import Pattern, TypeVar from beartype import beartype # type: ignore from beartype.typing import ( # type: ignore @@ -24,6 +24,7 @@ Iterator, List, Optional, + Protocol, Tuple, Union, cast, From 013d258b1696c7b07d5d17cc7e82b2d0cde214b9 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:22:08 -0400 Subject: [PATCH 47/90] use beartype protocol instead --- .github/workflows/main.yml | 2 +- requirements.in | 1 + requirements.txt | 2 ++ requirements_test.txt | 4 +++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f90a665..a413918 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,6 +36,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install typing-extensions beartype tox tox-gh-actions + pip install tox tox-gh-actions - name: Run tests run: tox -v diff --git a/requirements.in b/requirements.in index e370cca..f8fe16b 100644 --- a/requirements.in +++ b/requirements.in @@ -1 +1,2 @@ beartype +typing-extensions diff --git a/requirements.txt b/requirements.txt index fd32cff..d94285e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,5 @@ # beartype==0.10.4 # via -r requirements.in +typing-extensions==4.2.0 + # via -r requirements.in diff --git a/requirements_test.txt b/requirements_test.txt index b654c22..f52ea7a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1 +1,3 @@ -tox \ No newline at end of file +tox +typing-extensions +beartype From 5d8a4686155afb18dfd15fc548a2fdf2eb8fc6dd Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:25:13 -0400 Subject: [PATCH 48/90] us typing-extensions protocol if not in typing --- verbex/verbex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/verbex/verbex.py b/verbex/verbex.py index a1ca65a..2362835 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -9,10 +9,11 @@ from typing import ( # <--------------- if Python ≥ 3.9.0 Annotated, ParamSpec, + Protocol, TypeAlias, ) except ImportError: - from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 # noqa E501 + from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 # noqa E501 from typing import Pattern, TypeVar @@ -24,7 +25,6 @@ Iterator, List, Optional, - Protocol, Tuple, Union, cast, From f69a8f4c31410ffebd2bf86f35803d5a2c9382aa Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 12:28:08 -0400 Subject: [PATCH 49/90] use runtime checkable from typing-extensions for version 3.7 --- verbex/verbex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/verbex/verbex.py b/verbex/verbex.py index 2362835..c383643 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -11,9 +11,10 @@ ParamSpec, Protocol, TypeAlias, + runtime_checkable, ) except ImportError: - from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 # noqa E501 + from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable # type: ignore # <--- if Python < 3.9.0 # noqa E501 from typing import Pattern, TypeVar @@ -28,7 +29,6 @@ Tuple, Union, cast, - runtime_checkable, ) from beartype.vale import Is # type: ignore From 395b2b6d6b5449e8a5fd203bffeb4ad4d5b8dd4a Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 13:37:54 -0400 Subject: [PATCH 50/90] push to pypi.org --- MANIFEST.IN | 4 +- dist/Verbex-1.1.0.win-amd64.zip | Bin 12788 -> 27913 bytes setup.cfg | 1 - setup.py.old | 38 -- verbex/GPLv3_LICENSE.txt | 674 ++++++++++++++++++++++++++++++++ verbex/LICENSE.txt | 39 ++ 6 files changed, 715 insertions(+), 41 deletions(-) delete mode 100644 setup.py.old create mode 100644 verbex/GPLv3_LICENSE.txt create mode 100644 verbex/LICENSE.txt diff --git a/MANIFEST.IN b/MANIFEST.IN index 9714462..6ed0c64 100644 --- a/MANIFEST.IN +++ b/MANIFEST.IN @@ -1,3 +1,3 @@ include verbex/py.typed -include LICENSE.TXT -include GPLv3_LICENSE.txt +include verbex/LICENSE.TXT +include verbex/GPLv3_LICENSE.txt diff --git a/dist/Verbex-1.1.0.win-amd64.zip b/dist/Verbex-1.1.0.win-amd64.zip index 5c11901347bb1332b4914205efd0fcb1d8ef734f..374059f0fac7792baa899dd44959497e6369f7bf 100644 GIT binary patch delta 25705 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 CEc0pr delta 10702 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 diff --git a/setup.cfg b/setup.cfg index 0071b46..bf83e77 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,6 @@ long_description_content_type = text/markdown classifiers = License :: OSI Approved :: GNU General Public License v3 (GPLv3) Programming Language :: Python - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 diff --git a/setup.py.old b/setup.py.old deleted file mode 100644 index 34d1196..0000000 --- a/setup.py.old +++ /dev/null @@ -1,38 +0,0 @@ -from pathlib import Path - -from setuptools import setup - -SCRIPT_ROOT = Path(__file__).parent -long_description = (SCRIPT_ROOT / "README.md").read_text() - -setup( - name="Verbex", - version="1.0.3", - description=( - "Make difficult regular expressions easy! Python fork based on of the awesome" - " VerbalExpressions repo - https://github.com/jehna/VerbalExpressions" - ), - long_description=long_description, - long_description_content_type="text/markdown", - author=( - "Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer" - " Raghuram, Kharms, Richard Broderick" - ), - license="GPLv3", - url="https://github.com/rbroderi/Verbex", - test_suite="tests", - packages=["verbex"], - classifiers=[ - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Software Development :: Libraries", - "Topic :: Text Processing", - ], - include_package_data=True, -) diff --git a/verbex/GPLv3_LICENSE.txt b/verbex/GPLv3_LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/verbex/GPLv3_LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/verbex/LICENSE.txt b/verbex/LICENSE.txt new file mode 100644 index 0000000..4b5ad82 --- /dev/null +++ b/verbex/LICENSE.txt @@ -0,0 +1,39 @@ +Verbal Expressions +Copyright (C) 2022 Richard Broderick + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + + This file incorporates work covered by the following copyright and + permission notice: + + The MIT License (MIT) + + Copyright (c) 2017 jehna + + 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. From 82d23555912bb5b2e14cdea6d524d2c1a1c36563 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:23:55 -0400 Subject: [PATCH 51/90] added auto doc generate pre-commit hook --- .pre-commit-config.yaml | 16 + docs/index.html | 2 +- docs/search.js | 4 +- docs/verbex.html | 238 +++ docs/verbex/verbex.html | 3188 +++++++++++++++++++++------------------ requirements.in | 1 + requirements_dev.in | 1 + requirements_dev.txt | 10 + requirements_test.txt | 1 + setup.cfg | 2 +- verbex/__init__.py | 4 + 11 files changed, 1971 insertions(+), 1496 deletions(-) create mode 100644 docs/verbex.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bbf2403..fdf4b5d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,8 +11,10 @@ repos: hooks: - id: trailing-whitespace types: [file, text] + exclude_types: [html, javascript] - id: end-of-file-fixer types: [file, text] + exclude_types: [html, javascript] - id: check-case-conflict - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 @@ -197,3 +199,17 @@ repos: - id: forbid-bidi-controls types: [file] types_or: [python, powershell, lua, jinja] + - repo: local + hooks: + - id: pdoc + name: "Generate Documentation" + description: 'Auto generating documentation with pdoc' + entry: pdoc + args: [verbex, -o, docs] + language: python + language_version: python3 + require_serial: true + types: [python] + pass_filenames: false + additional_dependencies: + - beartype diff --git a/docs/index.html b/docs/index.html index 0e34fd0..97569ec 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,6 +2,6 @@ - + diff --git a/docs/search.js b/docs/search.js index 8bdd7cb..87f83cf 100644 --- a/docs/search.js +++ b/docs/search.js @@ -1,6 +1,6 @@ window.pdocSearch = (function(){ /** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oGenerate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n /,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; + /** pdoc search index */const docs = [{"fullname": "verbex", "modulename": "verbex", "type": "module", "doc": "

\n"}, {"fullname": "verbex.verbex", "modulename": "verbex.verbex", "type": "module", "doc": "

Generate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; // mirrored in build-search-index.js (part 1) // Also split on html tags. this is a cheap heuristic, but good enough. @@ -43,4 +43,4 @@ window.pdocSearch = (function(){ }, expand: true }); -})(); +})(); \ No newline at end of file diff --git a/docs/verbex.html b/docs/verbex.html new file mode 100644 index 0000000..189cab6 --- /dev/null +++ b/docs/verbex.html @@ -0,0 +1,238 @@ + + + + + + + verbex API documentation + + + + + + + + +
+
+
+

+verbex

+ + +
+ View Source +
0import importlib.metadata
+1
+2from .verbex import CharClass as CharClass
+3from .verbex import SpecialChar as SpecialChar
+4from .verbex import Verbex as Verbex
+5
+6__version__ = importlib.metadata.version("verbex")
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/docs/verbex/verbex.html b/docs/verbex/verbex.html index 16149cf..48f01fc 100644 --- a/docs/verbex/verbex.html +++ b/docs/verbex/verbex.html @@ -16,9 +16,16 @@

Generate regular expressions from an easier fluent verbal form.

@@ -247,617 +254,638 @@

8 from typing import ( # <--------------- if Python ≥ 3.9.0 9 Annotated, 10 ParamSpec, - 11 TypeAlias, - 12 ) - 13except (ModuleNotFoundError, ImportError): - 14 from typing_extensions import TypeAlias, Annotated, ParamSpec # type: ignore # <--- if Python < 3.9.0 - 15 - 16from typing import Pattern, Protocol, TypeVar + 11 Protocol, + 12 TypeAlias, + 13 runtime_checkable, + 14 ) + 15except ImportError: + 16 from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable # type: ignore # <--- if Python < 3.9.0 # noqa E501 17 - 18from beartype import beartype # type: ignore - 19from beartype.typing import ( # type: ignore - 20 Any, - 21 Callable, - 22 Dict, - 23 Iterator, - 24 List, - 25 Optional, - 26 Tuple, - 27 Union, - 28 cast, - 29 runtime_checkable, - 30) - 31from beartype.vale import Is # type: ignore - 32 + 18from typing import Pattern, TypeVar + 19 + 20from beartype import beartype # type: ignore + 21from beartype.typing import ( # type: ignore + 22 Any, + 23 Callable, + 24 Dict, + 25 Iterator, + 26 List, + 27 Optional, + 28 Tuple, + 29 Union, + 30 cast, + 31) + 32from beartype.vale import Is # type: ignore 33 - 34def _string_len_is_1(text: object) -> bool: - 35 return isinstance(text, str) and len(text) == 1 - 36 + 34 + 35def _string_len_is_1(text: object) -> bool: + 36 return isinstance(text, str) and len(text) == 1 37 - 38Char = Annotated[str, Is[_string_len_is_1]] - 39 + 38 + 39Char = Annotated[str, Is[_string_len_is_1]] 40 - 41P = ParamSpec("P") # noqa VNE001 - 42R = TypeVar("R") # noqa VNE001 - 43 + 41 + 42P = ParamSpec("P") # noqa VNE001 + 43R = TypeVar("R") # noqa VNE001 44 - 45# work around for bug https://github.com/python/mypy/issues/12660 - 46# fixed in next version of mypy - 47@runtime_checkable - 48class HasIter(Protocol): - 49 """Workaround for mypy P.args.""" - 50 - 51 def __iter__(self) -> Iterator[Any]: - 52 """Object can be iterated. - 53 - 54 Yields: - 55 Next object. - 56 """ - 57 ... - 58 + 45 + 46# work around for bug https://github.com/python/mypy/issues/12660 + 47# fixed in next version of mypy + 48@runtime_checkable + 49class HasIter(Protocol): + 50 """Workaround for mypy P.args.""" + 51 + 52 def __iter__(self) -> Iterator[Any]: + 53 """Object can be iterated. + 54 + 55 Yields: + 56 Next object. + 57 """ + 58 ... 59 - 60# work around for bug https://github.com/python/mypy/issues/12660 - 61# fixed in next version of mypy - 62@runtime_checkable - 63class HasItems(Protocol): - 64 """Workaround for mypy P.kwargs.""" - 65 - 66 def items(self) -> Tuple[str, Any]: - 67 """Object has items method. - 68 - 69 Returns: - 70 The dict of items. - 71 """ - 72 ... - 73 + 60 + 61# work around for bug https://github.com/python/mypy/issues/12660 + 62# fixed in next version of mypy + 63@runtime_checkable + 64class HasItems(Protocol): + 65 """Workaround for mypy P.kwargs.""" + 66 + 67 def items(self) -> Tuple[str, Any]: + 68 """Object has items method. + 69 + 70 Returns: + 71 The dict of items. + 72 """ + 73 ... 74 - 75class EscapedText(str): - 76 """Text that has been escaped for regex. - 77 - 78 Arguments: - 79 str -- Extend the string class. - 80 """ - 81 - 82 def __new__(cls, value: str) -> EscapedText: - 83 """Return a escaped regex string. - 84 - 85 Arguments: - 86 value -- the string to escape - 87 - 88 Returns: - 89 _description_ - 90 """ - 91 return str.__new__(cls, re.escape(value)) - 92 + 75 + 76class EscapedText(str): + 77 """Text that has been escaped for regex. + 78 + 79 Arguments: + 80 str -- Extend the string class. + 81 """ + 82 + 83 def __new__(cls, value: str) -> EscapedText: + 84 """Return a escaped regex string. + 85 + 86 Arguments: + 87 value -- the string to escape + 88 + 89 Returns: + 90 _description_ + 91 """ + 92 return str.__new__(cls, re.escape(value)) 93 - 94def re_escape(func: Callable[P, R]) -> Callable[P, R]: - 95 """Automatically escape any string parameters as EscapedText. - 96 - 97 Arguments: - 98 func -- The function to decorate. - 99 -100 Returns: -101 The decorated function. -102 """ -103 -104 @wraps(func) -105 def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore -106 escaped_args: List[Any] = [] -107 escaped_kwargs: Dict[str, Any] = {} -108 for arg in cast(HasIter, args): -109 if not isinstance(arg, EscapedText) and isinstance(arg, str): -110 escaped_args.append(EscapedText(arg)) -111 else: -112 escaped_args.append(arg) -113 arg_k: str -114 arg_v: Any -115 for arg_k, arg_v in cast(HasItems, kwargs).items(): -116 if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): -117 escaped_kwargs[arg_k] = EscapedText(str(arg_v)) -118 else: -119 escaped_kwargs[arg_k] = arg_v -120 return func(*escaped_args, **escaped_kwargs) # type: ignore -121 -122 return inner -123 + 94 + 95def re_escape(func: Callable[P, R]) -> Callable[P, R]: + 96 """Automatically escape any string parameters as EscapedText. + 97 + 98 Arguments: + 99 func -- The function to decorate. +100 +101 Returns: +102 The decorated function. +103 """ +104 +105 @wraps(func) +106 def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore +107 escaped_args: List[Any] = [] +108 escaped_kwargs: Dict[str, Any] = {} +109 for arg in cast(HasIter, args): +110 if not isinstance(arg, EscapedText) and isinstance(arg, str): +111 escaped_args.append(EscapedText(arg)) +112 else: +113 escaped_args.append(arg) +114 arg_k: str +115 arg_v: Any +116 for arg_k, arg_v in cast(HasItems, kwargs).items(): +117 if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): +118 escaped_kwargs[arg_k] = EscapedText(str(arg_v)) +119 else: +120 escaped_kwargs[arg_k] = arg_v +121 return func(*escaped_args, **escaped_kwargs) # type: ignore +122 +123 return inner 124 -125class CharClass(Enum): -126 """Enum of character classes in regex. -127 -128 Arguments: -129 Enum -- Extends the Enum class. -130 """ -131 -132 DIGIT = "\\d" -133 LETTER = "\\w" -134 UPPERCASE_LETTER = "\\u" -135 LOWERCASE_LETTER = "\\l" -136 WHITESPACE = "\\s" -137 TAB = "\\t" -138 -139 def __str__(self) -> str: -140 """To string method based on Enum value. -141 -142 Returns: -143 value of Enum -144 """ -145 return self.value -146 +125 +126class CharClass(Enum): +127 """Enum of character classes in regex. +128 +129 Arguments: +130 Enum -- Extends the Enum class. +131 """ +132 +133 DIGIT = "\\d" +134 LETTER = "\\w" +135 UPPERCASE_LETTER = "\\u" +136 LOWERCASE_LETTER = "\\l" +137 WHITESPACE = "\\s" +138 TAB = "\\t" +139 +140 def __str__(self) -> str: +141 """To string method based on Enum value. +142 +143 Returns: +144 value of Enum +145 """ +146 return self.value 147 -148class SpecialChar(Enum): -149 """Enum of special charaters, shorthand. -150 -151 Arguments: -152 Enum -- Extends the Enum class. -153 """ -154 -155 # does not work / should not be used in [ ] -156 LINEBREAK = "(\\n|(\\r\\n))" -157 START_OF_LINE = "^" -158 END_OF_LINE = "$" -159 TAB = "\t" -160 -161 def __str__(self) -> str: -162 """To string for special chars enum. -163 -164 Returns: -165 Return value of enum as string. -166 """ -167 return self.value -168 +148 +149class SpecialChar(Enum): +150 """Enum of special charaters, shorthand. +151 +152 Arguments: +153 Enum -- Extends the Enum class. +154 """ +155 +156 # does not work / should not be used in [ ] +157 LINEBREAK = "(\\n|(\\r\\n))" +158 START_OF_LINE = "^" +159 END_OF_LINE = "$" +160 TAB = "\t" +161 +162 def __str__(self) -> str: +163 """To string for special chars enum. +164 +165 Returns: +166 Return value of enum as string. +167 """ +168 return self.value 169 -170CharClassOrChars: TypeAlias = Union[str, CharClass] -171EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] -172VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] -173 +170 +171CharClassOrChars: TypeAlias = Union[str, CharClass] +172EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] +173VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] 174 -175class Verbex: -176 """ -177 VerbalExpressions class. -178 -179 the following methods do not try to match the original js lib! -180 """ -181 -182 EMPTY_REGEX_FLAG = re.RegexFlag(0) -183 -184 @re_escape -185 @beartype -186 def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): -187 """Create a Verbex object; setting any needed flags. -188 -189 Keyword Arguments: -190 modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) -191 """ -192 # self._parts: List[str] = [text] -193 self._parts: List[str] = [] -194 self._modifiers = modifiers +175 +176def _poseur_decorator(*poseur: Any) -> Any: +177 """Positional-only arguments runtime checker.""" +178 import functools +179 +180 def caller(func: Callable[P, R]) -> Callable[P, R]: # type: ignore +181 @functools.wraps(func) +182 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: +183 poseur_args = set(poseur).intersection(kwargs) # type: ignore +184 if poseur_args: +185 raise TypeError( +186 "%s() got some positional-only arguments passed as keyword" +187 " arguments: %r" % (func.__name__, ", ".join(poseur_args)), +188 ) +189 return func(*args, **kwargs) # type: ignore +190 +191 return wrapper +192 +193 return caller +194 195 -196 @property -197 def modifiers(self) -> re.RegexFlag: -198 """Return the modifiers for this Verbex object. +196class Verbex: +197 """ +198 VerbalExpressions class. 199 -200 Returns: -201 The modifiers applied to this object. -202 """ -203 return self._modifiers +200 the following methods do not try to match the original js lib! +201 """ +202 +203 EMPTY_REGEX_FLAG = re.RegexFlag(0) 204 -205 def __str__(self) -> str: -206 """Return regex string representation.""" -207 return "".join(self._parts) -208 -209 @beartype -210 def _add(self, value: Union[str, List[str]]) -> Verbex: -211 """ -212 Append a transformed value to internal expression to be compiled. -213 -214 As possible, this method should be "private". -215 """ -216 if isinstance(value, list): -217 self._parts.extend(value) -218 else: -219 self._parts.append(value) -220 return self -221 -222 def regex(self) -> Pattern[str]: -223 """Get a regular expression object.""" -224 return re.compile( -225 str(self), -226 self._modifiers, -227 ) -228 -229 # allow VerbexEscapedCharClassOrSpecial -230 -231 @re_escape -232 @beartype -233 def _capture_group_with_name( -234 self, -235 name: str, -236 text: VerbexEscapedCharClassOrSpecial, -237 ) -> Verbex: -238 return self._add(f"(?<{name}>{str(text)})") -239 -240 @re_escape -241 @beartype -242 def _capture_group_without_name( -243 self, -244 text: VerbexEscapedCharClassOrSpecial, -245 ) -> Verbex: -246 return self._add(f"({str(text)})") -247 -248 @re_escape -249 @beartype -250 def capture_group( -251 self, -252 /, -253 name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, -254 text: Optional[VerbexEscapedCharClassOrSpecial] = None, -255 ) -> Verbex: -256 """Create a capture group. -257 -258 Name is optional if not specified then the first argument is the text. -259 -260 Keyword Arguments: -261 name_or_text -- The name of the group / text to search for (default: {None}) -262 text -- The text to search for (default: {None}) -263 -264 Raises: -265 ValueError: If name is specified then text must be as well. -266 -267 Returns: -268 Verbex with added capture group. -269 """ -270 if name_or_text is not None: -271 if text is None: -272 _text = name_or_text -273 return self._capture_group_without_name(_text) -274 if isinstance(name_or_text, str): -275 return self._capture_group_with_name(name_or_text, text) -276 raise ValueError("text must be specified with optional name") -277 -278 @re_escape -279 @beartype -280 def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 -281 """`or` is a python keyword so we use `OR` instead. -282 -283 Arguments: -284 text -- Text to find or a Verbex object. -285 -286 Returns: -287 Modified Verbex object. -288 """ -289 return self._add("|").find(text) -290 -291 @re_escape -292 @beartype -293 def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -294 """Find the text or Verbex object zero or more times. -295 -296 Arguments: -297 text -- The text / Verbex object to look for. +205 @re_escape +206 @beartype +207 def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): +208 """Create a Verbex object; setting any needed flags. +209 +210 Keyword Arguments: +211 modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) +212 """ +213 # self._parts: List[str] = [text] +214 self._parts: List[str] = [] +215 self._modifiers = modifiers +216 +217 @property +218 def modifiers(self) -> re.RegexFlag: +219 """Return the modifiers for this Verbex object. +220 +221 Returns: +222 The modifiers applied to this object. +223 """ +224 return self._modifiers +225 +226 def __str__(self) -> str: +227 """Return regex string representation.""" +228 return "".join(self._parts) +229 +230 @beartype +231 def _add(self, value: Union[str, List[str]]) -> Verbex: +232 """ +233 Append a transformed value to internal expression to be compiled. +234 +235 As possible, this method should be "private". +236 """ +237 if isinstance(value, list): +238 self._parts.extend(value) +239 else: +240 self._parts.append(value) +241 return self +242 +243 def regex(self) -> Pattern[str]: +244 """Get a regular expression object.""" +245 return re.compile( +246 str(self), +247 self._modifiers, +248 ) +249 +250 # allow VerbexEscapedCharClassOrSpecial +251 +252 @re_escape +253 @beartype +254 def _capture_group_with_name( +255 self, +256 name: str, +257 text: VerbexEscapedCharClassOrSpecial, +258 ) -> Verbex: +259 return self._add(f"(?<{name}>{str(text)})") +260 +261 @re_escape +262 @beartype +263 def _capture_group_without_name( +264 self, +265 text: VerbexEscapedCharClassOrSpecial, +266 ) -> Verbex: +267 return self._add(f"({str(text)})") +268 +269 @re_escape +270 @beartype +271 @_poseur_decorator("self") +272 def capture_group( +273 self, +274 name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, +275 text: Optional[VerbexEscapedCharClassOrSpecial] = None, +276 ) -> Verbex: +277 """Create a capture group. +278 +279 Name is optional if not specified then the first argument is the text. +280 +281 Keyword Arguments: +282 name_or_text -- The name of the group / text to search for (default: {None}) +283 text -- The text to search for (default: {None}) +284 +285 Raises: +286 ValueError: If name is specified then text must be as well. +287 +288 Returns: +289 Verbex with added capture group. +290 """ +291 if name_or_text is not None: +292 if text is None: +293 _text = name_or_text +294 return self._capture_group_without_name(_text) +295 if isinstance(name_or_text, str): +296 return self._capture_group_with_name(name_or_text, text) +297 raise ValueError("text must be specified with optional name") 298 -299 Returns: -300 Modified Verbex object. -301 """ -302 return self._add(f"(?:{str(text)})*") +299 @re_escape +300 @beartype +301 def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 +302 """`or` is a python keyword so we use `OR` instead. 303 -304 @re_escape -305 @beartype -306 def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -307 """Find the text or Verbex object one or more times. -308 -309 Arguments: -310 text -- The text / Verbex object to look for. +304 Arguments: +305 text -- Text to find or a Verbex object. +306 +307 Returns: +308 Modified Verbex object. +309 """ +310 return self._add("|").find(text) 311 -312 Returns: -313 Modified Verbex object. -314 """ -315 return self._add(f"(?:{str(text)})+") +312 @re_escape +313 @beartype +314 def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +315 """Find the text or Verbex object zero or more times. 316 -317 @re_escape -318 @beartype -319 def n_times( -320 self, -321 text: VerbexEscapedCharClassOrSpecial, -322 n: int, # noqa: VNE001 -323 ) -> Verbex: -324 """Find the text or Verbex object n or more times. -325 -326 Arguments: -327 text -- The text / Verbex object to look for. -328 -329 Returns: -330 Modified Verbex object. -331 """ -332 return self._add(f"(?:{str(text)}){{{n}}}") -333 -334 @re_escape -335 @beartype -336 def n_times_or_more( -337 self, -338 text: VerbexEscapedCharClassOrSpecial, -339 n: int, # noqa: VNE001 -340 ) -> Verbex: -341 """Find the text or Verbex object at least n times. -342 -343 Arguments: -344 text -- The text / Verbex object to look for. -345 -346 Returns: -347 Modified Verbex object. -348 """ -349 return self._add(f"(?:{str(text)}){{{n},}}") -350 -351 @re_escape -352 @beartype -353 def n_to_m_times( -354 self, -355 text: VerbexEscapedCharClassOrSpecial, -356 n: int, # noqa: VNE001 -357 m: int, # noqa: VNE001 -358 ) -> Verbex: -359 """Find the text or Verbex object between n and m times. -360 -361 Arguments: -362 text -- The text / Verbex object to look for. +317 Arguments: +318 text -- The text / Verbex object to look for. +319 +320 Returns: +321 Modified Verbex object. +322 """ +323 return self._add(f"(?:{str(text)})*") +324 +325 @re_escape +326 @beartype +327 def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +328 """Find the text or Verbex object one or more times. +329 +330 Arguments: +331 text -- The text / Verbex object to look for. +332 +333 Returns: +334 Modified Verbex object. +335 """ +336 return self._add(f"(?:{str(text)})+") +337 +338 @re_escape +339 @beartype +340 def n_times( +341 self, +342 text: VerbexEscapedCharClassOrSpecial, +343 n: int, # noqa: VNE001 +344 ) -> Verbex: +345 """Find the text or Verbex object n or more times. +346 +347 Arguments: +348 text -- The text / Verbex object to look for. +349 +350 Returns: +351 Modified Verbex object. +352 """ +353 return self._add(f"(?:{str(text)}){{{n}}}") +354 +355 @re_escape +356 @beartype +357 def n_times_or_more( +358 self, +359 text: VerbexEscapedCharClassOrSpecial, +360 n: int, # noqa: VNE001 +361 ) -> Verbex: +362 """Find the text or Verbex object at least n times. 363 -364 Returns: -365 Modified Verbex object. -366 """ -367 return self._add(f"(?:{str(text)}){{{n},{m}}}") -368 -369 @re_escape -370 @beartype -371 def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -372 """Possibly find the text / Verbex object. -373 -374 Arguments: -375 text -- The text / Verbex object to possibly find. -376 -377 Returns: -378 Modified Verbex object. -379 """ -380 return self._add(f"(?:{str(text)})?") +364 Arguments: +365 text -- The text / Verbex object to look for. +366 +367 Returns: +368 Modified Verbex object. +369 """ +370 return self._add(f"(?:{str(text)}){{{n},}}") +371 +372 @re_escape +373 @beartype +374 def n_to_m_times( +375 self, +376 text: VerbexEscapedCharClassOrSpecial, +377 n: int, # noqa: VNE001 +378 m: int, # noqa: VNE001 +379 ) -> Verbex: +380 """Find the text or Verbex object between n and m times. 381 -382 @re_escape -383 @beartype -384 def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -385 """Find the text or Verbex object. -386 -387 Arguments: -388 text -- The text / Verbex object to look for. +382 Arguments: +383 text -- The text / Verbex object to look for. +384 +385 Returns: +386 Modified Verbex object. +387 """ +388 return self._add(f"(?:{str(text)}){{{n},{m}}}") 389 -390 Returns: -391 Modified Verbex object. -392 """ -393 return self._add(str(text)) +390 @re_escape +391 @beartype +392 def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +393 """Possibly find the text / Verbex object. 394 -395 @re_escape -396 @beartype -397 def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -398 """Synonym for find. -399 -400 Arguments: -401 text -- The text / Verbex object to look for. +395 Arguments: +396 text -- The text / Verbex object to possibly find. +397 +398 Returns: +399 Modified Verbex object. +400 """ +401 return self._add(f"(?:{str(text)})?") 402 -403 Returns: -404 Modified Verbex object. -405 """ -406 return self.find(text) +403 @re_escape +404 @beartype +405 def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +406 """Find the text or Verbex object. 407 -408 @re_escape -409 @beartype -410 def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -411 """Match if string is followed by text. -412 -413 Positive lookahead -414 -415 Returns: -416 Modified Verbex object. -417 """ -418 return self._add(f"(?={text})") -419 -420 @re_escape -421 @beartype -422 def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -423 """Match if string is not followed by text. -424 -425 Negative lookahead -426 -427 Returns: -428 Modified Verbex object. -429 """ -430 return self._add(f"(?!{text})") -431 -432 @re_escape -433 @beartype -434 def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -435 """Match if string is not preceded by text. -436 -437 Positive lookbehind -438 -439 Returns: -440 Modified Verbex object. -441 """ -442 return self._add(f"(?<={text})") -443 -444 @re_escape -445 @beartype -446 def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -447 """Match if string is not preceded by text. -448 -449 Negative Lookbehind -450 -451 Returns: -452 Modified Verbex object. -453 """ -454 return self._add(f"(?<!{text})") -455 -456 # only allow CharclassOrChars +408 Arguments: +409 text -- The text / Verbex object to look for. +410 +411 Returns: +412 Modified Verbex object. +413 """ +414 return self._add(str(text)) +415 +416 @re_escape +417 @beartype +418 def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +419 """Synonym for find. +420 +421 Arguments: +422 text -- The text / Verbex object to look for. +423 +424 Returns: +425 Modified Verbex object. +426 """ +427 return self.find(text) +428 +429 @re_escape +430 @beartype +431 def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +432 """Match if string is followed by text. +433 +434 Positive lookahead +435 +436 Returns: +437 Modified Verbex object. +438 """ +439 return self._add(f"(?={text})") +440 +441 @re_escape +442 @beartype +443 def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +444 """Match if string is not followed by text. +445 +446 Negative lookahead +447 +448 Returns: +449 Modified Verbex object. +450 """ +451 return self._add(f"(?!{text})") +452 +453 @re_escape +454 @beartype +455 def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +456 """Match if string is not preceded by text. 457 -458 @re_escape -459 @beartype -460 def any_of(self, chargroup: CharClassOrChars) -> Verbex: -461 """Find anything in this group of chars or char class. -462 -463 Arguments: -464 text -- The characters to look for. -465 -466 Returns: -467 Modified Verbex object. -468 """ -469 return self._add(f"(?:[{chargroup}])") -470 -471 @re_escape -472 @beartype -473 def not_any_of(self, text: CharClassOrChars) -> Verbex: -474 """Find anything but this group of chars or char class. -475 -476 Arguments: -477 text -- The characters to not look for. +458 Positive lookbehind +459 +460 Returns: +461 Modified Verbex object. +462 """ +463 return self._add(f"(?<={text})") +464 +465 @re_escape +466 @beartype +467 def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +468 """Match if string is not preceded by text. +469 +470 Negative Lookbehind +471 +472 Returns: +473 Modified Verbex object. +474 """ +475 return self._add(f"(?<!{text})") +476 +477 # only allow CharclassOrChars 478 -479 Returns: -480 Modified Verbex object. -481 """ -482 return self._add(f"(?:[^{text}])") +479 @re_escape +480 @beartype +481 def any_of(self, chargroup: CharClassOrChars) -> Verbex: +482 """Find anything in this group of chars or char class. 483 -484 @re_escape -485 def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: -486 """Find anything one or more times but this group of chars or char class. -487 -488 Arguments: -489 text -- The characters to not look for. -490 -491 Returns: -492 Modified Verbex object. -493 """ -494 return self._add(f"[^{chargroup}]+") -495 -496 # no text input -497 -498 def start_of_line(self) -> Verbex: -499 """Find the start of the line. -500 -501 Returns: -502 Modified Verbex object. -503 """ -504 return self.find(SpecialChar.START_OF_LINE) -505 -506 def end_of_line(self) -> Verbex: -507 """Find the end of the line. +484 Arguments: +485 text -- The characters to look for. +486 +487 Returns: +488 Modified Verbex object. +489 """ +490 return self._add(f"(?:[{chargroup}])") +491 +492 @re_escape +493 @beartype +494 def not_any_of(self, text: CharClassOrChars) -> Verbex: +495 """Find anything but this group of chars or char class. +496 +497 Arguments: +498 text -- The characters to not look for. +499 +500 Returns: +501 Modified Verbex object. +502 """ +503 return self._add(f"(?:[^{text}])") +504 +505 @re_escape +506 def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: +507 """Find anything one or more times but this group of chars or char class. 508 -509 Returns: -510 Modified Verbex object. -511 """ -512 return self.find(SpecialChar.END_OF_LINE) -513 -514 def line_break(self) -> Verbex: -515 """Find a line break. +509 Arguments: +510 text -- The characters to not look for. +511 +512 Returns: +513 Modified Verbex object. +514 """ +515 return self._add(f"[^{chargroup}]+") 516 -517 Returns: -518 Modified Verbex object. -519 """ -520 return self.find(SpecialChar.LINEBREAK) +517 # no text input +518 +519 def start_of_line(self) -> Verbex: +520 """Find the start of the line. 521 -522 def tab(self) -> Verbex: -523 """Find a tab. -524 -525 Returns: -526 Modified Verbex object. -527 """ -528 return self.find(SpecialChar.TAB) +522 Returns: +523 Modified Verbex object. +524 """ +525 return self.find(SpecialChar.START_OF_LINE) +526 +527 def end_of_line(self) -> Verbex: +528 """Find the end of the line. 529 -530 def anything(self) -> Verbex: -531 """Find anything one or more time. -532 -533 Returns: -534 Modified Verbex object. -535 """ -536 return self._add(".+") +530 Returns: +531 Modified Verbex object. +532 """ +533 return self.find(SpecialChar.END_OF_LINE) +534 +535 def line_break(self) -> Verbex: +536 """Find a line break. 537 -538 def as_few(self) -> Verbex: -539 """Modify previous search to not be greedy. -540 -541 Returns: -542 Modified Verbex object. -543 """ -544 return self._add("?") +538 Returns: +539 Modified Verbex object. +540 """ +541 return self.find(SpecialChar.LINEBREAK) +542 +543 def tab(self) -> Verbex: +544 """Find a tab. 545 -546 @beartype -547 def number_range(self, start: int, end: int) -> Verbex: -548 """Generate a range of numbers. -549 -550 Arguments: -551 start -- Start of the range -552 end -- End of the range +546 Returns: +547 Modified Verbex object. +548 """ +549 return self.find(SpecialChar.TAB) +550 +551 def anything(self) -> Verbex: +552 """Find anything one or more time. 553 554 Returns: 555 Modified Verbex object. 556 """ -557 return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") +557 return self._add(".+") 558 -559 @beartype -560 def letter_range(self, start: Char, end: Char) -> Verbex: -561 """Generate a range of letters. -562 -563 Arguments: -564 start -- Start of the range -565 end -- End of the range +559 def as_few(self) -> Verbex: +560 """Modify previous search to not be greedy. +561 +562 Returns: +563 Modified Verbex object. +564 """ +565 return self._add("?") 566 -567 Returns: -568 Modified Verbex object. -569 """ -570 return self._add(f"[{start}-{end}]") -571 -572 def word(self) -> Verbex: -573 """Find a word on word boundary. +567 @beartype +568 def number_range(self, start: int, end: int) -> Verbex: +569 """Generate a range of numbers. +570 +571 Arguments: +572 start -- Start of the range +573 end -- End of the range 574 575 Returns: 576 Modified Verbex object. 577 """ -578 return self._add("(\\b\\w+\\b)") +578 return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") 579 -580 # # --------------- modifiers ------------------------ -581 -582 def with_any_case(self) -> Verbex: -583 """Modify Verbex object to be case insensitive. -584 -585 Returns: -586 Modified Verbex object. -587 """ -588 self._modifiers |= re.IGNORECASE -589 return self -590 -591 def search_by_line(self) -> Verbex: -592 """Search each line, ^ and $ match begining and end of line respectively. -593 -594 Returns: -595 Modified Verbex object. -596 """ -597 self._modifiers |= re.MULTILINE -598 return self -599 -600 def with_ascii(self) -> Verbex: -601 """Match ascii instead of unicode. +580 @beartype +581 def letter_range(self, start: Char, end: Char) -> Verbex: +582 """Generate a range of letters. +583 +584 Arguments: +585 start -- Start of the range +586 end -- End of the range +587 +588 Returns: +589 Modified Verbex object. +590 """ +591 return self._add(f"[{start}-{end}]") +592 +593 def word(self) -> Verbex: +594 """Find a word on word boundary. +595 +596 Returns: +597 Modified Verbex object. +598 """ +599 return self._add("(\\b\\w+\\b)") +600 +601 # # --------------- modifiers ------------------------ 602 -603 Returns: -604 Modified Verbex object. -605 """ -606 self._modifiers |= re.ASCII -607 return self -608 -609 -610# left over notes from original version -611# def __getattr__(self, attr): -612# """ any other function will be sent to the regex object """ -613# regex = self.regex() -614# return getattr(regex, attr) -615 -616# def replace(self, string, repl): -617# return self.sub(repl, string) -618 -619 -620if __name__ == "__main__": -621 pass +603 def with_any_case(self) -> Verbex: +604 """Modify Verbex object to be case insensitive. +605 +606 Returns: +607 Modified Verbex object. +608 """ +609 self._modifiers |= re.IGNORECASE +610 return self +611 +612 def search_by_line(self) -> Verbex: +613 """Search each line, ^ and $ match begining and end of line respectively. +614 +615 Returns: +616 Modified Verbex object. +617 """ +618 self._modifiers |= re.MULTILINE +619 return self +620 +621 def with_ascii(self) -> Verbex: +622 """Match ascii instead of unicode. +623 +624 Returns: +625 Modified Verbex object. +626 """ +627 self._modifiers |= re.ASCII +628 return self +629 +630 +631# left over notes from original version +632# def __getattr__(self, attr): +633# """ any other function will be sent to the regex object """ +634# regex = self.regex() +635# return getattr(regex, attr) +636 +637# def replace(self, string, repl): +638# return self.sub(repl, string) +639 +640 +641if __name__ == "__main__": +642 pass @@ -869,8 +897,8 @@

P = ~P - - + +
@@ -885,17 +913,17 @@

View Source -
48@runtime_checkable
-49class HasIter(Protocol):
-50    """Workaround for mypy P.args."""
-51
-52    def __iter__(self) -> Iterator[Any]:
-53        """Object can be iterated.
-54
-55        Yields:
-56            Next object.
-57        """
-58        ...
+            
49@runtime_checkable
+50class HasIter(Protocol):
+51    """Workaround for mypy P.args."""
+52
+53    def __iter__(self) -> Iterator[Any]:
+54        """Object can be iterated.
+55
+56        Yields:
+57            Next object.
+58        """
+59        ...
 
@@ -907,7 +935,7 @@

#   - + HasIter(*args, **kwargs)
@@ -944,7 +972,7 @@

- +

@@ -960,17 +988,17 @@

View Source -
63@runtime_checkable
-64class HasItems(Protocol):
-65    """Workaround for mypy P.kwargs."""
-66
-67    def items(self) -> Tuple[str, Any]:
-68        """Object has items method.
-69
-70        Returns:
-71            The dict of items.
-72        """
-73        ...
+            
64@runtime_checkable
+65class HasItems(Protocol):
+66    """Workaround for mypy P.kwargs."""
+67
+68    def items(self) -> Tuple[str, Any]:
+69        """Object has items method.
+70
+71        Returns:
+72            The dict of items.
+73        """
+74        ...
 
@@ -982,7 +1010,7 @@

#   - + HasItems(*args, **kwargs)
@@ -1019,26 +1047,26 @@

- +

#   - + def items(self) -> tuple[str, typing.Any]:
View Source -
67    def items(self) -> Tuple[str, Any]:
-68        """Object has items method.
-69
-70        Returns:
-71            The dict of items.
-72        """
-73        ...
+            
68    def items(self) -> Tuple[str, Any]:
+69        """Object has items method.
+70
+71        Returns:
+72            The dict of items.
+73        """
+74        ...
 
@@ -1056,30 +1084,30 @@

#   - + class EscapedText(builtins.str):
View Source -
76class EscapedText(str):
-77    """Text that has been escaped for regex.
-78
-79    Arguments:
-80        str -- Extend the string class.
-81    """
-82
-83    def __new__(cls, value: str) -> EscapedText:
-84        """Return a escaped regex string.
-85
-86        Arguments:
-87            value -- the string to escape
-88
-89        Returns:
-90            _description_
-91        """
-92        return str.__new__(cls, re.escape(value))
+            
77class EscapedText(str):
+78    """Text that has been escaped for regex.
+79
+80    Arguments:
+81        str -- Extend the string class.
+82    """
+83
+84    def __new__(cls, value: str) -> EscapedText:
+85        """Return a escaped regex string.
+86
+87        Arguments:
+88            value -- the string to escape
+89
+90        Returns:
+91            _description_
+92        """
+93        return str.__new__(cls, re.escape(value))
 
@@ -1094,22 +1122,22 @@

#   - + EscapedText(value: str)
View Source -
83    def __new__(cls, value: str) -> EscapedText:
-84        """Return a escaped regex string.
-85
-86        Arguments:
-87            value -- the string to escape
-88
-89        Returns:
-90            _description_
-91        """
-92        return str.__new__(cls, re.escape(value))
+            
84    def __new__(cls, value: str) -> EscapedText:
+85        """Return a escaped regex string.
+86
+87        Arguments:
+88            value -- the string to escape
+89
+90        Returns:
+91            _description_
+92        """
+93        return str.__new__(cls, re.escape(value))
 
@@ -1184,7 +1212,7 @@
Inherited Members
#   - + def re_escape( func: collections.abc.Callable[~P, ~R] @@ -1193,35 +1221,35 @@
Inherited Members
View Source -
 95def re_escape(func: Callable[P, R]) -> Callable[P, R]:
- 96    """Automatically escape any string parameters as EscapedText.
- 97
- 98    Arguments:
- 99        func -- The function to decorate.
-100
-101    Returns:
-102        The decorated function.
-103    """
-104
-105    @wraps(func)
-106    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-107        escaped_args: List[Any] = []
-108        escaped_kwargs: Dict[str, Any] = {}
-109        for arg in cast(HasIter, args):
-110            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-111                escaped_args.append(EscapedText(arg))
-112            else:
-113                escaped_args.append(arg)
-114        arg_k: str
-115        arg_v: Any
-116        for arg_k, arg_v in cast(HasItems, kwargs).items():
-117            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-118                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-119            else:
-120                escaped_kwargs[arg_k] = arg_v
-121        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-122
-123    return inner
+            
 96def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+ 97    """Automatically escape any string parameters as EscapedText.
+ 98
+ 99    Arguments:
+100        func -- The function to decorate.
+101
+102    Returns:
+103        The decorated function.
+104    """
+105
+106    @wraps(func)
+107    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+108        escaped_args: List[Any] = []
+109        escaped_kwargs: Dict[str, Any] = {}
+110        for arg in cast(HasIter, args):
+111            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+112                escaped_args.append(EscapedText(arg))
+113            else:
+114                escaped_args.append(arg)
+115        arg_k: str
+116        arg_v: Any
+117        for arg_k, arg_v in cast(HasItems, kwargs).items():
+118            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+119                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+120            else:
+121                escaped_kwargs[arg_k] = arg_v
+122        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+123
+124    return inner
 
@@ -1241,34 +1269,34 @@
Inherited Members
#   - + class CharClass(enum.Enum):
View Source -
126class CharClass(Enum):
-127    """Enum of character classes in regex.
-128
-129    Arguments:
-130        Enum -- Extends the Enum class.
-131    """
-132
-133    DIGIT = "\\d"
-134    LETTER = "\\w"
-135    UPPERCASE_LETTER = "\\u"
-136    LOWERCASE_LETTER = "\\l"
-137    WHITESPACE = "\\s"
-138    TAB = "\\t"
-139
-140    def __str__(self) -> str:
-141        """To string method based on Enum value.
-142
-143        Returns:
-144            value of Enum
-145        """
-146        return self.value
+            
127class CharClass(Enum):
+128    """Enum of character classes in regex.
+129
+130    Arguments:
+131        Enum -- Extends the Enum class.
+132    """
+133
+134    DIGIT = "\\d"
+135    LETTER = "\\w"
+136    UPPERCASE_LETTER = "\\u"
+137    LOWERCASE_LETTER = "\\l"
+138    WHITESPACE = "\\s"
+139    TAB = "\\t"
+140
+141    def __str__(self) -> str:
+142        """To string method based on Enum value.
+143
+144        Returns:
+145            value of Enum
+146        """
+147        return self.value
 
@@ -1286,8 +1314,8 @@
Inherited Members
DIGIT = <CharClass.DIGIT: '\\d'>
- - + +
@@ -1296,8 +1324,8 @@
Inherited Members
LETTER = <CharClass.LETTER: '\\w'>
- - + +

@@ -1306,8 +1334,8 @@
Inherited Members
UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'>
- - + +
@@ -1316,8 +1344,8 @@
Inherited Members
LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'>
- - + +
@@ -1326,8 +1354,8 @@
Inherited Members
WHITESPACE = <CharClass.WHITESPACE: '\\s'>
- - + +
@@ -1336,8 +1364,8 @@
Inherited Members
TAB = <CharClass.TAB: '\\t'>
- - + +
@@ -1355,33 +1383,33 @@
Inherited Members
#   - + class SpecialChar(enum.Enum):
View Source -
149class SpecialChar(Enum):
-150    """Enum of special charaters, shorthand.
-151
-152    Arguments:
-153        Enum -- Extends the Enum class.
-154    """
-155
-156    # does not work  / should not be used in [ ]
-157    LINEBREAK = "(\\n|(\\r\\n))"
-158    START_OF_LINE = "^"
-159    END_OF_LINE = "$"
-160    TAB = "\t"
-161
-162    def __str__(self) -> str:
-163        """To string for special chars enum.
-164
-165        Returns:
-166            Return value of enum as string.
-167        """
-168        return self.value
+            
150class SpecialChar(Enum):
+151    """Enum of special charaters, shorthand.
+152
+153    Arguments:
+154        Enum -- Extends the Enum class.
+155    """
+156
+157    # does not work  / should not be used in [ ]
+158    LINEBREAK = "(\\n|(\\r\\n))"
+159    START_OF_LINE = "^"
+160    END_OF_LINE = "$"
+161    TAB = "\t"
+162
+163    def __str__(self) -> str:
+164        """To string for special chars enum.
+165
+166        Returns:
+167            Return value of enum as string.
+168        """
+169        return self.value
 
@@ -1399,8 +1427,8 @@
Inherited Members
LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'>
- - + +
@@ -1409,8 +1437,8 @@
Inherited Members
START_OF_LINE = <SpecialChar.START_OF_LINE: '^'>
- - + +
@@ -1419,8 +1447,8 @@
Inherited Members
END_OF_LINE = <SpecialChar.END_OF_LINE: '$'>
- - + +
@@ -1429,8 +1457,8 @@
Inherited Members
TAB = <SpecialChar.TAB: '\t'>
- - + +
@@ -1450,8 +1478,8 @@
Inherited Members
CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass]
- - + +
@@ -1460,8 +1488,8 @@
Inherited Members
EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] - - + +
@@ -1470,454 +1498,454 @@
Inherited Members
VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] - - + +
#   - + class Verbex:
View Source -
176class Verbex:
-177    """
-178    VerbalExpressions class.
-179
-180    the following methods do not try to match the original js lib!
-181    """
-182
-183    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-184
-185    @re_escape
-186    @beartype
-187    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-188        """Create a Verbex object; setting any needed flags.
-189
-190        Keyword Arguments:
-191            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-192        """
-193        # self._parts: List[str] = [text]
-194        self._parts: List[str] = []
-195        self._modifiers = modifiers
-196
-197    @property
-198    def modifiers(self) -> re.RegexFlag:
-199        """Return the modifiers for this Verbex object.
+            
197class Verbex:
+198    """
+199    VerbalExpressions class.
 200
-201        Returns:
-202            The modifiers applied to this object.
-203        """
-204        return self._modifiers
+201    the following methods do not try to match the original js lib!
+202    """
+203
+204    EMPTY_REGEX_FLAG = re.RegexFlag(0)
 205
-206    def __str__(self) -> str:
-207        """Return regex string representation."""
-208        return "".join(self._parts)
-209
-210    @beartype
-211    def _add(self, value: Union[str, List[str]]) -> Verbex:
-212        """
-213        Append a transformed value to internal expression to be compiled.
-214
-215        As possible, this method should be "private".
-216        """
-217        if isinstance(value, list):
-218            self._parts.extend(value)
-219        else:
-220            self._parts.append(value)
-221        return self
-222
-223    def regex(self) -> Pattern[str]:
-224        """Get a regular expression object."""
-225        return re.compile(
-226            str(self),
-227            self._modifiers,
-228        )
-229
-230    # allow VerbexEscapedCharClassOrSpecial
-231
-232    @re_escape
-233    @beartype
-234    def _capture_group_with_name(
-235        self,
-236        name: str,
-237        text: VerbexEscapedCharClassOrSpecial,
-238    ) -> Verbex:
-239        return self._add(f"(?<{name}>{str(text)})")
-240
-241    @re_escape
-242    @beartype
-243    def _capture_group_without_name(
-244        self,
-245        text: VerbexEscapedCharClassOrSpecial,
-246    ) -> Verbex:
-247        return self._add(f"({str(text)})")
-248
-249    @re_escape
-250    @beartype
-251    def capture_group(
-252        self,
-253        /,
-254        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-255        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-256    ) -> Verbex:
-257        """Create a capture group.
-258
-259        Name is optional if not specified then the first argument is the text.
-260
-261        Keyword Arguments:
-262            name_or_text -- The name of the group / text to search for (default: {None})
-263            text -- The text to search for (default: {None})
-264
-265        Raises:
-266            ValueError: If name is specified then text must be as well.
-267
-268        Returns:
-269            Verbex with added capture group.
-270        """
-271        if name_or_text is not None:
-272            if text is None:
-273                _text = name_or_text
-274                return self._capture_group_without_name(_text)
-275            if isinstance(name_or_text, str):
-276                return self._capture_group_with_name(name_or_text, text)
-277        raise ValueError("text must be specified with optional name")
-278
-279    @re_escape
-280    @beartype
-281    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-282        """`or` is a python keyword so we use `OR` instead.
-283
-284        Arguments:
-285            text -- Text to find or a Verbex object.
-286
-287        Returns:
-288            Modified Verbex object.
-289        """
-290        return self._add("|").find(text)
-291
-292    @re_escape
-293    @beartype
-294    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-295        """Find the text or Verbex object zero or more times.
-296
-297        Arguments:
-298            text -- The text / Verbex object to look for.
+206    @re_escape
+207    @beartype
+208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+209        """Create a Verbex object; setting any needed flags.
+210
+211        Keyword Arguments:
+212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+213        """
+214        # self._parts: List[str] = [text]
+215        self._parts: List[str] = []
+216        self._modifiers = modifiers
+217
+218    @property
+219    def modifiers(self) -> re.RegexFlag:
+220        """Return the modifiers for this Verbex object.
+221
+222        Returns:
+223            The modifiers applied to this object.
+224        """
+225        return self._modifiers
+226
+227    def __str__(self) -> str:
+228        """Return regex string representation."""
+229        return "".join(self._parts)
+230
+231    @beartype
+232    def _add(self, value: Union[str, List[str]]) -> Verbex:
+233        """
+234        Append a transformed value to internal expression to be compiled.
+235
+236        As possible, this method should be "private".
+237        """
+238        if isinstance(value, list):
+239            self._parts.extend(value)
+240        else:
+241            self._parts.append(value)
+242        return self
+243
+244    def regex(self) -> Pattern[str]:
+245        """Get a regular expression object."""
+246        return re.compile(
+247            str(self),
+248            self._modifiers,
+249        )
+250
+251    # allow VerbexEscapedCharClassOrSpecial
+252
+253    @re_escape
+254    @beartype
+255    def _capture_group_with_name(
+256        self,
+257        name: str,
+258        text: VerbexEscapedCharClassOrSpecial,
+259    ) -> Verbex:
+260        return self._add(f"(?<{name}>{str(text)})")
+261
+262    @re_escape
+263    @beartype
+264    def _capture_group_without_name(
+265        self,
+266        text: VerbexEscapedCharClassOrSpecial,
+267    ) -> Verbex:
+268        return self._add(f"({str(text)})")
+269
+270    @re_escape
+271    @beartype
+272    @_poseur_decorator("self")
+273    def capture_group(
+274        self,
+275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+277    ) -> Verbex:
+278        """Create a capture group.
+279
+280        Name is optional if not specified then the first argument is the text.
+281
+282        Keyword Arguments:
+283            name_or_text -- The name of the group / text to search for (default: {None})
+284            text -- The text to search for (default: {None})
+285
+286        Raises:
+287            ValueError: If name is specified then text must be as well.
+288
+289        Returns:
+290            Verbex with added capture group.
+291        """
+292        if name_or_text is not None:
+293            if text is None:
+294                _text = name_or_text
+295                return self._capture_group_without_name(_text)
+296            if isinstance(name_or_text, str):
+297                return self._capture_group_with_name(name_or_text, text)
+298        raise ValueError("text must be specified with optional name")
 299
-300        Returns:
-301            Modified Verbex object.
-302        """
-303        return self._add(f"(?:{str(text)})*")
+300    @re_escape
+301    @beartype
+302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+303        """`or` is a python keyword so we use `OR` instead.
 304
-305    @re_escape
-306    @beartype
-307    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-308        """Find the text or Verbex object one or more times.
-309
-310        Arguments:
-311            text -- The text / Verbex object to look for.
+305        Arguments:
+306            text -- Text to find or a Verbex object.
+307
+308        Returns:
+309            Modified Verbex object.
+310        """
+311        return self._add("|").find(text)
 312
-313        Returns:
-314            Modified Verbex object.
-315        """
-316        return self._add(f"(?:{str(text)})+")
+313    @re_escape
+314    @beartype
+315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+316        """Find the text or Verbex object zero or more times.
 317
-318    @re_escape
-319    @beartype
-320    def n_times(
-321        self,
-322        text: VerbexEscapedCharClassOrSpecial,
-323        n: int,  # noqa: VNE001
-324    ) -> Verbex:
-325        """Find the text or Verbex object n or more times.
-326
-327        Arguments:
-328            text -- The text / Verbex object to look for.
-329
-330        Returns:
-331            Modified Verbex object.
-332        """
-333        return self._add(f"(?:{str(text)}){{{n}}}")
-334
-335    @re_escape
-336    @beartype
-337    def n_times_or_more(
-338        self,
-339        text: VerbexEscapedCharClassOrSpecial,
-340        n: int,  # noqa: VNE001
-341    ) -> Verbex:
-342        """Find the text or Verbex object at least n times.
-343
-344        Arguments:
-345            text -- The text / Verbex object to look for.
-346
-347        Returns:
-348            Modified Verbex object.
-349        """
-350        return self._add(f"(?:{str(text)}){{{n},}}")
-351
-352    @re_escape
-353    @beartype
-354    def n_to_m_times(
-355        self,
-356        text: VerbexEscapedCharClassOrSpecial,
-357        n: int,  # noqa: VNE001
-358        m: int,  # noqa: VNE001
-359    ) -> Verbex:
-360        """Find the text or Verbex object between n and m times.
-361
-362        Arguments:
-363            text -- The text / Verbex object to look for.
+318        Arguments:
+319            text -- The text / Verbex object to look for.
+320
+321        Returns:
+322            Modified Verbex object.
+323        """
+324        return self._add(f"(?:{str(text)})*")
+325
+326    @re_escape
+327    @beartype
+328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+329        """Find the text or Verbex object one or more times.
+330
+331        Arguments:
+332            text -- The text / Verbex object to look for.
+333
+334        Returns:
+335            Modified Verbex object.
+336        """
+337        return self._add(f"(?:{str(text)})+")
+338
+339    @re_escape
+340    @beartype
+341    def n_times(
+342        self,
+343        text: VerbexEscapedCharClassOrSpecial,
+344        n: int,  # noqa: VNE001
+345    ) -> Verbex:
+346        """Find the text or Verbex object n or more times.
+347
+348        Arguments:
+349            text -- The text / Verbex object to look for.
+350
+351        Returns:
+352            Modified Verbex object.
+353        """
+354        return self._add(f"(?:{str(text)}){{{n}}}")
+355
+356    @re_escape
+357    @beartype
+358    def n_times_or_more(
+359        self,
+360        text: VerbexEscapedCharClassOrSpecial,
+361        n: int,  # noqa: VNE001
+362    ) -> Verbex:
+363        """Find the text or Verbex object at least n times.
 364
-365        Returns:
-366            Modified Verbex object.
-367        """
-368        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-369
-370    @re_escape
-371    @beartype
-372    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-373        """Possibly find the text / Verbex object.
-374
-375        Arguments:
-376            text -- The text / Verbex object to possibly find.
-377
-378        Returns:
-379            Modified Verbex object.
-380        """
-381        return self._add(f"(?:{str(text)})?")
+365        Arguments:
+366            text -- The text / Verbex object to look for.
+367
+368        Returns:
+369            Modified Verbex object.
+370        """
+371        return self._add(f"(?:{str(text)}){{{n},}}")
+372
+373    @re_escape
+374    @beartype
+375    def n_to_m_times(
+376        self,
+377        text: VerbexEscapedCharClassOrSpecial,
+378        n: int,  # noqa: VNE001
+379        m: int,  # noqa: VNE001
+380    ) -> Verbex:
+381        """Find the text or Verbex object between n and m times.
 382
-383    @re_escape
-384    @beartype
-385    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-386        """Find the text or Verbex object.
-387
-388        Arguments:
-389            text -- The text / Verbex object to look for.
+383        Arguments:
+384            text -- The text / Verbex object to look for.
+385
+386        Returns:
+387            Modified Verbex object.
+388        """
+389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
 390
-391        Returns:
-392            Modified Verbex object.
-393        """
-394        return self._add(str(text))
+391    @re_escape
+392    @beartype
+393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+394        """Possibly find the text / Verbex object.
 395
-396    @re_escape
-397    @beartype
-398    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-399        """Synonym for find.
-400
-401        Arguments:
-402            text -- The text / Verbex object to look for.
+396        Arguments:
+397            text -- The text / Verbex object to possibly find.
+398
+399        Returns:
+400            Modified Verbex object.
+401        """
+402        return self._add(f"(?:{str(text)})?")
 403
-404        Returns:
-405            Modified Verbex object.
-406        """
-407        return self.find(text)
+404    @re_escape
+405    @beartype
+406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+407        """Find the text or Verbex object.
 408
-409    @re_escape
-410    @beartype
-411    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-412        """Match if string is followed by text.
-413
-414        Positive lookahead
-415
-416        Returns:
-417            Modified Verbex object.
-418        """
-419        return self._add(f"(?={text})")
-420
-421    @re_escape
-422    @beartype
-423    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-424        """Match if string is not followed by text.
-425
-426        Negative lookahead
-427
-428        Returns:
-429            Modified Verbex object.
-430        """
-431        return self._add(f"(?!{text})")
-432
-433    @re_escape
-434    @beartype
-435    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-436        """Match if string is not preceded by text.
-437
-438        Positive lookbehind
-439
-440        Returns:
-441            Modified Verbex object.
-442        """
-443        return self._add(f"(?<={text})")
-444
-445    @re_escape
-446    @beartype
-447    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-448        """Match if string is not preceded by text.
-449
-450        Negative Lookbehind
-451
-452        Returns:
-453            Modified Verbex object.
-454        """
-455        return self._add(f"(?<!{text})")
-456
-457    # only allow CharclassOrChars
+409        Arguments:
+410            text -- The text / Verbex object to look for.
+411
+412        Returns:
+413            Modified Verbex object.
+414        """
+415        return self._add(str(text))
+416
+417    @re_escape
+418    @beartype
+419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+420        """Synonym for find.
+421
+422        Arguments:
+423            text -- The text / Verbex object to look for.
+424
+425        Returns:
+426            Modified Verbex object.
+427        """
+428        return self.find(text)
+429
+430    @re_escape
+431    @beartype
+432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+433        """Match if string is followed by text.
+434
+435        Positive lookahead
+436
+437        Returns:
+438            Modified Verbex object.
+439        """
+440        return self._add(f"(?={text})")
+441
+442    @re_escape
+443    @beartype
+444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+445        """Match if string is not followed by text.
+446
+447        Negative lookahead
+448
+449        Returns:
+450            Modified Verbex object.
+451        """
+452        return self._add(f"(?!{text})")
+453
+454    @re_escape
+455    @beartype
+456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+457        """Match if string is not preceded by text.
 458
-459    @re_escape
-460    @beartype
-461    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-462        """Find anything in this group of chars or char class.
-463
-464        Arguments:
-465            text -- The characters to look for.
-466
-467        Returns:
-468            Modified Verbex object.
-469        """
-470        return self._add(f"(?:[{chargroup}])")
-471
-472    @re_escape
-473    @beartype
-474    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-475        """Find anything but this group of chars or char class.
-476
-477        Arguments:
-478            text -- The characters to not look for.
+459        Positive lookbehind
+460
+461        Returns:
+462            Modified Verbex object.
+463        """
+464        return self._add(f"(?<={text})")
+465
+466    @re_escape
+467    @beartype
+468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+469        """Match if string is not preceded by text.
+470
+471        Negative Lookbehind
+472
+473        Returns:
+474            Modified Verbex object.
+475        """
+476        return self._add(f"(?<!{text})")
+477
+478    # only allow CharclassOrChars
 479
-480        Returns:
-481            Modified Verbex object.
-482        """
-483        return self._add(f"(?:[^{text}])")
+480    @re_escape
+481    @beartype
+482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+483        """Find anything in this group of chars or char class.
 484
-485    @re_escape
-486    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-487        """Find anything one or more times but this group of chars or char class.
-488
-489        Arguments:
-490            text -- The characters to not look for.
-491
-492        Returns:
-493            Modified Verbex object.
-494        """
-495        return self._add(f"[^{chargroup}]+")
-496
-497    # no text input
-498
-499    def start_of_line(self) -> Verbex:
-500        """Find the start of the line.
-501
-502        Returns:
-503            Modified Verbex object.
-504        """
-505        return self.find(SpecialChar.START_OF_LINE)
-506
-507    def end_of_line(self) -> Verbex:
-508        """Find the end of the line.
+485        Arguments:
+486            text -- The characters to look for.
+487
+488        Returns:
+489            Modified Verbex object.
+490        """
+491        return self._add(f"(?:[{chargroup}])")
+492
+493    @re_escape
+494    @beartype
+495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+496        """Find anything but this group of chars or char class.
+497
+498        Arguments:
+499            text -- The characters to not look for.
+500
+501        Returns:
+502            Modified Verbex object.
+503        """
+504        return self._add(f"(?:[^{text}])")
+505
+506    @re_escape
+507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+508        """Find anything one or more times but this group of chars or char class.
 509
-510        Returns:
-511            Modified Verbex object.
-512        """
-513        return self.find(SpecialChar.END_OF_LINE)
-514
-515    def line_break(self) -> Verbex:
-516        """Find a line break.
+510        Arguments:
+511            text -- The characters to not look for.
+512
+513        Returns:
+514            Modified Verbex object.
+515        """
+516        return self._add(f"[^{chargroup}]+")
 517
-518        Returns:
-519            Modified Verbex object.
-520        """
-521        return self.find(SpecialChar.LINEBREAK)
+518    # no text input
+519
+520    def start_of_line(self) -> Verbex:
+521        """Find the start of the line.
 522
-523    def tab(self) -> Verbex:
-524        """Find a tab.
-525
-526        Returns:
-527            Modified Verbex object.
-528        """
-529        return self.find(SpecialChar.TAB)
+523        Returns:
+524            Modified Verbex object.
+525        """
+526        return self.find(SpecialChar.START_OF_LINE)
+527
+528    def end_of_line(self) -> Verbex:
+529        """Find the end of the line.
 530
-531    def anything(self) -> Verbex:
-532        """Find anything one or more time.
-533
-534        Returns:
-535            Modified Verbex object.
-536        """
-537        return self._add(".+")
+531        Returns:
+532            Modified Verbex object.
+533        """
+534        return self.find(SpecialChar.END_OF_LINE)
+535
+536    def line_break(self) -> Verbex:
+537        """Find a line break.
 538
-539    def as_few(self) -> Verbex:
-540        """Modify previous search to not be greedy.
-541
-542        Returns:
-543            Modified Verbex object.
-544        """
-545        return self._add("?")
+539        Returns:
+540            Modified Verbex object.
+541        """
+542        return self.find(SpecialChar.LINEBREAK)
+543
+544    def tab(self) -> Verbex:
+545        """Find a tab.
 546
-547    @beartype
-548    def number_range(self, start: int, end: int) -> Verbex:
-549        """Generate a range of numbers.
-550
-551        Arguments:
-552            start -- Start of the range
-553            end -- End of the range
+547        Returns:
+548            Modified Verbex object.
+549        """
+550        return self.find(SpecialChar.TAB)
+551
+552    def anything(self) -> Verbex:
+553        """Find anything one or more time.
 554
 555        Returns:
 556            Modified Verbex object.
 557        """
-558        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+558        return self._add(".+")
 559
-560    @beartype
-561    def letter_range(self, start: Char, end: Char) -> Verbex:
-562        """Generate a range of letters.
-563
-564        Arguments:
-565            start -- Start of the range
-566            end -- End of the range
+560    def as_few(self) -> Verbex:
+561        """Modify previous search to not be greedy.
+562
+563        Returns:
+564            Modified Verbex object.
+565        """
+566        return self._add("?")
 567
-568        Returns:
-569            Modified Verbex object.
-570        """
-571        return self._add(f"[{start}-{end}]")
-572
-573    def word(self) -> Verbex:
-574        """Find a word on word boundary.
+568    @beartype
+569    def number_range(self, start: int, end: int) -> Verbex:
+570        """Generate a range of numbers.
+571
+572        Arguments:
+573            start -- Start of the range
+574            end -- End of the range
 575
 576        Returns:
 577            Modified Verbex object.
 578        """
-579        return self._add("(\\b\\w+\\b)")
+579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
 580
-581    # # --------------- modifiers ------------------------
-582
-583    def with_any_case(self) -> Verbex:
-584        """Modify Verbex object to be case insensitive.
-585
-586        Returns:
-587            Modified Verbex object.
-588        """
-589        self._modifiers |= re.IGNORECASE
-590        return self
-591
-592    def search_by_line(self) -> Verbex:
-593        """Search each line, ^ and $ match begining and end of line respectively.
-594
-595        Returns:
-596            Modified Verbex object.
-597        """
-598        self._modifiers |= re.MULTILINE
-599        return self
-600
-601    def with_ascii(self) -> Verbex:
-602        """Match ascii instead of unicode.
+581    @beartype
+582    def letter_range(self, start: Char, end: Char) -> Verbex:
+583        """Generate a range of letters.
+584
+585        Arguments:
+586            start -- Start of the range
+587            end -- End of the range
+588
+589        Returns:
+590            Modified Verbex object.
+591        """
+592        return self._add(f"[{start}-{end}]")
+593
+594    def word(self) -> Verbex:
+595        """Find a word on word boundary.
+596
+597        Returns:
+598            Modified Verbex object.
+599        """
+600        return self._add("(\\b\\w+\\b)")
+601
+602    # # --------------- modifiers ------------------------
 603
-604        Returns:
-605            Modified Verbex object.
-606        """
-607        self._modifiers |= re.ASCII
-608        return self
+604    def with_any_case(self) -> Verbex:
+605        """Modify Verbex object to be case insensitive.
+606
+607        Returns:
+608            Modified Verbex object.
+609        """
+610        self._modifiers |= re.IGNORECASE
+611        return self
+612
+613    def search_by_line(self) -> Verbex:
+614        """Search each line, ^ and $ match begining and end of line respectively.
+615
+616        Returns:
+617            Modified Verbex object.
+618        """
+619        self._modifiers |= re.MULTILINE
+620        return self
+621
+622    def with_ascii(self) -> Verbex:
+623        """Match ascii instead of unicode.
+624
+625        Returns:
+626            Modified Verbex object.
+627        """
+628        self._modifiers |= re.ASCII
+629        return self
 
@@ -1939,17 +1967,17 @@
Inherited Members
View Source -
185    @re_escape
-186    @beartype
-187    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-188        """Create a Verbex object; setting any needed flags.
-189
-190        Keyword Arguments:
-191            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-192        """
-193        # self._parts: List[str] = [text]
-194        self._parts: List[str] = []
-195        self._modifiers = modifiers
+            
206    @re_escape
+207    @beartype
+208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+209        """Create a Verbex object; setting any needed flags.
+210
+211        Keyword Arguments:
+212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+213        """
+214        # self._parts: List[str] = [text]
+215        self._parts: List[str] = []
+216        self._modifiers = modifiers
 
@@ -1968,8 +1996,8 @@
Inherited Members
EMPTY_REGEX_FLAG = - - + +
@@ -1978,7 +2006,7 @@
Inherited Members
modifiers: re.RegexFlag
- +

Return the modifiers for this Verbex object.

Returns: @@ -1990,19 +2018,19 @@

Inherited Members
#   - + def regex(self) -> Pattern[str]:
View Source -
223    def regex(self) -> Pattern[str]:
-224        """Get a regular expression object."""
-225        return re.compile(
-226            str(self),
-227            self._modifiers,
-228        )
+            
244    def regex(self) -> Pattern[str]:
+245        """Get a regular expression object."""
+246        return re.compile(
+247            str(self),
+248            self._modifiers,
+249        )
 
@@ -2021,7 +2049,6 @@
Inherited Members
def capture_group( self, - /, name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None, text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None ) -> verbex.verbex.Verbex: @@ -2029,35 +2056,35 @@
Inherited Members
View Source -
249    @re_escape
-250    @beartype
-251    def capture_group(
-252        self,
-253        /,
-254        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-255        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-256    ) -> Verbex:
-257        """Create a capture group.
-258
-259        Name is optional if not specified then the first argument is the text.
-260
-261        Keyword Arguments:
-262            name_or_text -- The name of the group / text to search for (default: {None})
-263            text -- The text to search for (default: {None})
-264
-265        Raises:
-266            ValueError: If name is specified then text must be as well.
-267
-268        Returns:
-269            Verbex with added capture group.
-270        """
-271        if name_or_text is not None:
-272            if text is None:
-273                _text = name_or_text
-274                return self._capture_group_without_name(_text)
-275            if isinstance(name_or_text, str):
-276                return self._capture_group_with_name(name_or_text, text)
-277        raise ValueError("text must be specified with optional name")
+            
270    @re_escape
+271    @beartype
+272    @_poseur_decorator("self")
+273    def capture_group(
+274        self,
+275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+277    ) -> Verbex:
+278        """Create a capture group.
+279
+280        Name is optional if not specified then the first argument is the text.
+281
+282        Keyword Arguments:
+283            name_or_text -- The name of the group / text to search for (default: {None})
+284            text -- The text to search for (default: {None})
+285
+286        Raises:
+287            ValueError: If name is specified then text must be as well.
+288
+289        Returns:
+290            Verbex with added capture group.
+291        """
+292        if name_or_text is not None:
+293            if text is None:
+294                _text = name_or_text
+295                return self._capture_group_without_name(_text)
+296            if isinstance(name_or_text, str):
+297                return self._capture_group_with_name(name_or_text, text)
+298        raise ValueError("text must be specified with optional name")
 
@@ -2094,18 +2121,18 @@
Inherited Members
View Source -
279    @re_escape
-280    @beartype
-281    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-282        """`or` is a python keyword so we use `OR` instead.
-283
-284        Arguments:
-285            text -- Text to find or a Verbex object.
-286
-287        Returns:
-288            Modified Verbex object.
-289        """
-290        return self._add("|").find(text)
+            
300    @re_escape
+301    @beartype
+302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+303        """`or` is a python keyword so we use `OR` instead.
+304
+305        Arguments:
+306            text -- Text to find or a Verbex object.
+307
+308        Returns:
+309            Modified Verbex object.
+310        """
+311        return self._add("|").find(text)
 
@@ -2136,18 +2163,18 @@
Inherited Members
View Source -
292    @re_escape
-293    @beartype
-294    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-295        """Find the text or Verbex object zero or more times.
-296
-297        Arguments:
-298            text -- The text / Verbex object to look for.
-299
-300        Returns:
-301            Modified Verbex object.
-302        """
-303        return self._add(f"(?:{str(text)})*")
+            
313    @re_escape
+314    @beartype
+315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+316        """Find the text or Verbex object zero or more times.
+317
+318        Arguments:
+319            text -- The text / Verbex object to look for.
+320
+321        Returns:
+322            Modified Verbex object.
+323        """
+324        return self._add(f"(?:{str(text)})*")
 
@@ -2178,18 +2205,18 @@
Inherited Members
View Source -
305    @re_escape
-306    @beartype
-307    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-308        """Find the text or Verbex object one or more times.
-309
-310        Arguments:
-311            text -- The text / Verbex object to look for.
-312
-313        Returns:
-314            Modified Verbex object.
-315        """
-316        return self._add(f"(?:{str(text)})+")
+            
326    @re_escape
+327    @beartype
+328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+329        """Find the text or Verbex object one or more times.
+330
+331        Arguments:
+332            text -- The text / Verbex object to look for.
+333
+334        Returns:
+335            Modified Verbex object.
+336        """
+337        return self._add(f"(?:{str(text)})+")
 
@@ -2221,22 +2248,22 @@
Inherited Members
View Source -
318    @re_escape
-319    @beartype
-320    def n_times(
-321        self,
-322        text: VerbexEscapedCharClassOrSpecial,
-323        n: int,  # noqa: VNE001
-324    ) -> Verbex:
-325        """Find the text or Verbex object n or more times.
-326
-327        Arguments:
-328            text -- The text / Verbex object to look for.
-329
-330        Returns:
-331            Modified Verbex object.
-332        """
-333        return self._add(f"(?:{str(text)}){{{n}}}")
+            
339    @re_escape
+340    @beartype
+341    def n_times(
+342        self,
+343        text: VerbexEscapedCharClassOrSpecial,
+344        n: int,  # noqa: VNE001
+345    ) -> Verbex:
+346        """Find the text or Verbex object n or more times.
+347
+348        Arguments:
+349            text -- The text / Verbex object to look for.
+350
+351        Returns:
+352            Modified Verbex object.
+353        """
+354        return self._add(f"(?:{str(text)}){{{n}}}")
 
@@ -2268,22 +2295,22 @@
Inherited Members
View Source -
335    @re_escape
-336    @beartype
-337    def n_times_or_more(
-338        self,
-339        text: VerbexEscapedCharClassOrSpecial,
-340        n: int,  # noqa: VNE001
-341    ) -> Verbex:
-342        """Find the text or Verbex object at least n times.
-343
-344        Arguments:
-345            text -- The text / Verbex object to look for.
-346
-347        Returns:
-348            Modified Verbex object.
-349        """
-350        return self._add(f"(?:{str(text)}){{{n},}}")
+            
356    @re_escape
+357    @beartype
+358    def n_times_or_more(
+359        self,
+360        text: VerbexEscapedCharClassOrSpecial,
+361        n: int,  # noqa: VNE001
+362    ) -> Verbex:
+363        """Find the text or Verbex object at least n times.
+364
+365        Arguments:
+366            text -- The text / Verbex object to look for.
+367
+368        Returns:
+369            Modified Verbex object.
+370        """
+371        return self._add(f"(?:{str(text)}){{{n},}}")
 
@@ -2316,23 +2343,23 @@
Inherited Members
View Source -
352    @re_escape
-353    @beartype
-354    def n_to_m_times(
-355        self,
-356        text: VerbexEscapedCharClassOrSpecial,
-357        n: int,  # noqa: VNE001
-358        m: int,  # noqa: VNE001
-359    ) -> Verbex:
-360        """Find the text or Verbex object between n and m times.
-361
-362        Arguments:
-363            text -- The text / Verbex object to look for.
-364
-365        Returns:
-366            Modified Verbex object.
-367        """
-368        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+            
373    @re_escape
+374    @beartype
+375    def n_to_m_times(
+376        self,
+377        text: VerbexEscapedCharClassOrSpecial,
+378        n: int,  # noqa: VNE001
+379        m: int,  # noqa: VNE001
+380    ) -> Verbex:
+381        """Find the text or Verbex object between n and m times.
+382
+383        Arguments:
+384            text -- The text / Verbex object to look for.
+385
+386        Returns:
+387            Modified Verbex object.
+388        """
+389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
 
@@ -2363,18 +2390,18 @@
Inherited Members
View Source -
370    @re_escape
-371    @beartype
-372    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-373        """Possibly find the text / Verbex object.
-374
-375        Arguments:
-376            text -- The text / Verbex object to possibly find.
-377
-378        Returns:
-379            Modified Verbex object.
-380        """
-381        return self._add(f"(?:{str(text)})?")
+            
391    @re_escape
+392    @beartype
+393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+394        """Possibly find the text / Verbex object.
+395
+396        Arguments:
+397            text -- The text / Verbex object to possibly find.
+398
+399        Returns:
+400            Modified Verbex object.
+401        """
+402        return self._add(f"(?:{str(text)})?")
 
@@ -2405,18 +2432,18 @@
Inherited Members
View Source -
383    @re_escape
-384    @beartype
-385    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-386        """Find the text or Verbex object.
-387
-388        Arguments:
-389            text -- The text / Verbex object to look for.
-390
-391        Returns:
-392            Modified Verbex object.
-393        """
-394        return self._add(str(text))
+            
404    @re_escape
+405    @beartype
+406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+407        """Find the text or Verbex object.
+408
+409        Arguments:
+410            text -- The text / Verbex object to look for.
+411
+412        Returns:
+413            Modified Verbex object.
+414        """
+415        return self._add(str(text))
 
@@ -2447,18 +2474,18 @@
Inherited Members
View Source -
396    @re_escape
-397    @beartype
-398    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-399        """Synonym for find.
-400
-401        Arguments:
-402            text -- The text / Verbex object to look for.
-403
-404        Returns:
-405            Modified Verbex object.
-406        """
-407        return self.find(text)
+            
417    @re_escape
+418    @beartype
+419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+420        """Synonym for find.
+421
+422        Arguments:
+423            text -- The text / Verbex object to look for.
+424
+425        Returns:
+426            Modified Verbex object.
+427        """
+428        return self.find(text)
 
@@ -2489,17 +2516,17 @@
Inherited Members
View Source -
409    @re_escape
-410    @beartype
-411    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-412        """Match if string is followed by text.
-413
-414        Positive lookahead
-415
-416        Returns:
-417            Modified Verbex object.
-418        """
-419        return self._add(f"(?={text})")
+            
430    @re_escape
+431    @beartype
+432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+433        """Match if string is followed by text.
+434
+435        Positive lookahead
+436
+437        Returns:
+438            Modified Verbex object.
+439        """
+440        return self._add(f"(?={text})")
 
@@ -2529,17 +2556,17 @@
Inherited Members
View Source -
421    @re_escape
-422    @beartype
-423    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-424        """Match if string is not followed by text.
-425
-426        Negative lookahead
-427
-428        Returns:
-429            Modified Verbex object.
-430        """
-431        return self._add(f"(?!{text})")
+            
442    @re_escape
+443    @beartype
+444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+445        """Match if string is not followed by text.
+446
+447        Negative lookahead
+448
+449        Returns:
+450            Modified Verbex object.
+451        """
+452        return self._add(f"(?!{text})")
 
@@ -2569,17 +2596,17 @@
Inherited Members
View Source -
433    @re_escape
-434    @beartype
-435    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-436        """Match if string is not preceded by text.
-437
-438        Positive lookbehind
-439
-440        Returns:
-441            Modified Verbex object.
-442        """
-443        return self._add(f"(?<={text})")
+            
454    @re_escape
+455    @beartype
+456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+457        """Match if string is not preceded by text.
+458
+459        Positive lookbehind
+460
+461        Returns:
+462            Modified Verbex object.
+463        """
+464        return self._add(f"(?<={text})")
 
@@ -2609,17 +2636,17 @@
Inherited Members
View Source -
445    @re_escape
-446    @beartype
-447    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-448        """Match if string is not preceded by text.
-449
-450        Negative Lookbehind
-451
-452        Returns:
-453            Modified Verbex object.
-454        """
-455        return self._add(f"(?<!{text})")
+            
466    @re_escape
+467    @beartype
+468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+469        """Match if string is not preceded by text.
+470
+471        Negative Lookbehind
+472
+473        Returns:
+474            Modified Verbex object.
+475        """
+476        return self._add(f"(?<!{text})")
 
@@ -2649,18 +2676,18 @@
Inherited Members
View Source -
459    @re_escape
-460    @beartype
-461    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-462        """Find anything in this group of chars or char class.
-463
-464        Arguments:
-465            text -- The characters to look for.
-466
-467        Returns:
-468            Modified Verbex object.
-469        """
-470        return self._add(f"(?:[{chargroup}])")
+            
480    @re_escape
+481    @beartype
+482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+483        """Find anything in this group of chars or char class.
+484
+485        Arguments:
+486            text -- The characters to look for.
+487
+488        Returns:
+489            Modified Verbex object.
+490        """
+491        return self._add(f"(?:[{chargroup}])")
 
@@ -2691,18 +2718,18 @@
Inherited Members
View Source -
472    @re_escape
-473    @beartype
-474    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-475        """Find anything but this group of chars or char class.
-476
-477        Arguments:
-478            text -- The characters to not look for.
-479
-480        Returns:
-481            Modified Verbex object.
-482        """
-483        return self._add(f"(?:[^{text}])")
+            
493    @re_escape
+494    @beartype
+495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+496        """Find anything but this group of chars or char class.
+497
+498        Arguments:
+499            text -- The characters to not look for.
+500
+501        Returns:
+502            Modified Verbex object.
+503        """
+504        return self._add(f"(?:[^{text}])")
 
@@ -2732,17 +2759,17 @@
Inherited Members
View Source -
485    @re_escape
-486    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-487        """Find anything one or more times but this group of chars or char class.
-488
-489        Arguments:
-490            text -- The characters to not look for.
-491
-492        Returns:
-493            Modified Verbex object.
-494        """
-495        return self._add(f"[^{chargroup}]+")
+            
506    @re_escape
+507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+508        """Find anything one or more times but this group of chars or char class.
+509
+510        Arguments:
+511            text -- The characters to not look for.
+512
+513        Returns:
+514            Modified Verbex object.
+515        """
+516        return self._add(f"[^{chargroup}]+")
 
@@ -2761,20 +2788,20 @@
Inherited Members
#   - + def start_of_line(self) -> verbex.verbex.Verbex:
View Source -
499    def start_of_line(self) -> Verbex:
-500        """Find the start of the line.
-501
-502        Returns:
-503            Modified Verbex object.
-504        """
-505        return self.find(SpecialChar.START_OF_LINE)
+            
520    def start_of_line(self) -> Verbex:
+521        """Find the start of the line.
+522
+523        Returns:
+524            Modified Verbex object.
+525        """
+526        return self.find(SpecialChar.START_OF_LINE)
 
@@ -2790,20 +2817,20 @@
Inherited Members
#   - + def end_of_line(self) -> verbex.verbex.Verbex:
View Source -
507    def end_of_line(self) -> Verbex:
-508        """Find the end of the line.
-509
-510        Returns:
-511            Modified Verbex object.
-512        """
-513        return self.find(SpecialChar.END_OF_LINE)
+            
528    def end_of_line(self) -> Verbex:
+529        """Find the end of the line.
+530
+531        Returns:
+532            Modified Verbex object.
+533        """
+534        return self.find(SpecialChar.END_OF_LINE)
 
@@ -2819,20 +2846,20 @@
Inherited Members
#   - + def line_break(self) -> verbex.verbex.Verbex:
View Source -
515    def line_break(self) -> Verbex:
-516        """Find a line break.
-517
-518        Returns:
-519            Modified Verbex object.
-520        """
-521        return self.find(SpecialChar.LINEBREAK)
+            
536    def line_break(self) -> Verbex:
+537        """Find a line break.
+538
+539        Returns:
+540            Modified Verbex object.
+541        """
+542        return self.find(SpecialChar.LINEBREAK)
 
@@ -2848,20 +2875,20 @@
Inherited Members
#   - + def tab(self) -> verbex.verbex.Verbex:
View Source -
523    def tab(self) -> Verbex:
-524        """Find a tab.
-525
-526        Returns:
-527            Modified Verbex object.
-528        """
-529        return self.find(SpecialChar.TAB)
+            
544    def tab(self) -> Verbex:
+545        """Find a tab.
+546
+547        Returns:
+548            Modified Verbex object.
+549        """
+550        return self.find(SpecialChar.TAB)
 
@@ -2877,20 +2904,20 @@
Inherited Members
#   - + def anything(self) -> verbex.verbex.Verbex:
View Source -
531    def anything(self) -> Verbex:
-532        """Find anything one or more time.
-533
-534        Returns:
-535            Modified Verbex object.
-536        """
-537        return self._add(".+")
+            
552    def anything(self) -> Verbex:
+553        """Find anything one or more time.
+554
+555        Returns:
+556            Modified Verbex object.
+557        """
+558        return self._add(".+")
 
@@ -2906,20 +2933,20 @@
Inherited Members
#   - + def as_few(self) -> verbex.verbex.Verbex:
View Source -
539    def as_few(self) -> Verbex:
-540        """Modify previous search to not be greedy.
-541
-542        Returns:
-543            Modified Verbex object.
-544        """
-545        return self._add("?")
+            
560    def as_few(self) -> Verbex:
+561        """Modify previous search to not be greedy.
+562
+563        Returns:
+564            Modified Verbex object.
+565        """
+566        return self._add("?")
 
@@ -2943,18 +2970,18 @@
Inherited Members
View Source -
547    @beartype
-548    def number_range(self, start: int, end: int) -> Verbex:
-549        """Generate a range of numbers.
-550
-551        Arguments:
-552            start -- Start of the range
-553            end -- End of the range
-554
-555        Returns:
-556            Modified Verbex object.
-557        """
-558        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+            
568    @beartype
+569    def number_range(self, start: int, end: int) -> Verbex:
+570        """Generate a range of numbers.
+571
+572        Arguments:
+573            start -- Start of the range
+574            end -- End of the range
+575
+576        Returns:
+577            Modified Verbex object.
+578        """
+579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
 
@@ -2986,18 +3013,18 @@
Inherited Members
View Source -
560    @beartype
-561    def letter_range(self, start: Char, end: Char) -> Verbex:
-562        """Generate a range of letters.
-563
-564        Arguments:
-565            start -- Start of the range
-566            end -- End of the range
-567
-568        Returns:
-569            Modified Verbex object.
-570        """
-571        return self._add(f"[{start}-{end}]")
+            
581    @beartype
+582    def letter_range(self, start: Char, end: Char) -> Verbex:
+583        """Generate a range of letters.
+584
+585        Arguments:
+586            start -- Start of the range
+587            end -- End of the range
+588
+589        Returns:
+590            Modified Verbex object.
+591        """
+592        return self._add(f"[{start}-{end}]")
 
@@ -3017,20 +3044,20 @@
Inherited Members
#   - + def word(self) -> verbex.verbex.Verbex:
View Source -
573    def word(self) -> Verbex:
-574        """Find a word on word boundary.
-575
-576        Returns:
-577            Modified Verbex object.
-578        """
-579        return self._add("(\\b\\w+\\b)")
+            
594    def word(self) -> Verbex:
+595        """Find a word on word boundary.
+596
+597        Returns:
+598            Modified Verbex object.
+599        """
+600        return self._add("(\\b\\w+\\b)")
 
@@ -3046,21 +3073,21 @@
Inherited Members
#   - + def with_any_case(self) -> verbex.verbex.Verbex:
View Source -
583    def with_any_case(self) -> Verbex:
-584        """Modify Verbex object to be case insensitive.
-585
-586        Returns:
-587            Modified Verbex object.
-588        """
-589        self._modifiers |= re.IGNORECASE
-590        return self
+            
604    def with_any_case(self) -> Verbex:
+605        """Modify Verbex object to be case insensitive.
+606
+607        Returns:
+608            Modified Verbex object.
+609        """
+610        self._modifiers |= re.IGNORECASE
+611        return self
 
@@ -3076,21 +3103,21 @@
Inherited Members
#   - + def search_by_line(self) -> verbex.verbex.Verbex:
View Source -
592    def search_by_line(self) -> Verbex:
-593        """Search each line, ^ and $ match begining and end of line respectively.
-594
-595        Returns:
-596            Modified Verbex object.
-597        """
-598        self._modifiers |= re.MULTILINE
-599        return self
+            
613    def search_by_line(self) -> Verbex:
+614        """Search each line, ^ and $ match begining and end of line respectively.
+615
+616        Returns:
+617            Modified Verbex object.
+618        """
+619        self._modifiers |= re.MULTILINE
+620        return self
 
@@ -3106,21 +3133,21 @@
Inherited Members
#   - + def with_ascii(self) -> verbex.verbex.Verbex:
View Source -
601    def with_ascii(self) -> Verbex:
-602        """Match ascii instead of unicode.
-603
-604        Returns:
-605            Modified Verbex object.
-606        """
-607        self._modifiers |= re.ASCII
-608        return self
+            
622    def with_ascii(self) -> Verbex:
+623        """Match ascii instead of unicode.
+624
+625        Returns:
+626            Modified Verbex object.
+627        """
+628        self._modifiers |= re.ASCII
+629        return self
 
@@ -3135,5 +3162,182 @@
Inherited Members
- - + + \ No newline at end of file diff --git a/requirements.in b/requirements.in index f8fe16b..07ac3c6 100644 --- a/requirements.in +++ b/requirements.in @@ -1,2 +1,3 @@ beartype typing-extensions +importlib-metadata; python_version < '3.7' diff --git a/requirements_dev.in b/requirements_dev.in index 29b6f24..31f1e1e 100644 --- a/requirements_dev.in +++ b/requirements_dev.in @@ -2,3 +2,4 @@ tox mypy>=0.950 black pre-commit +pdoc diff --git a/requirements_dev.txt b/requirements_dev.txt index 520c051..ef02568 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -22,6 +22,12 @@ filelock==3.6.0 # virtualenv identify==2.5.0 # via pre-commit +jinja2==3.1.2 + # via pdoc +markupsafe==2.1.1 + # via + # jinja2 + # pdoc mypy==0.950 # via -r requirements_dev.in mypy-extensions==0.4.3 @@ -34,6 +40,8 @@ packaging==21.3 # via tox pathspec==0.9.0 # via black +pdoc==11.2.0 + # via -r requirements_dev.in platformdirs==2.5.2 # via # black @@ -44,6 +52,8 @@ pre-commit==2.19.0 # via -r requirements_dev.in py==1.11.0 # via tox +pygments==2.12.0 + # via pdoc pyparsing==3.0.8 # via packaging pyyaml==6.0 diff --git a/requirements_test.txt b/requirements_test.txt index f52ea7a..37fcfa7 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,4 @@ tox typing-extensions beartype +importlib-metadata; python_version < '3.7' diff --git a/setup.cfg b/setup.cfg index bf83e77..a957fff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = Verbex -version = 1.1.0 +version = 1.2.0 author = Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer Raghuram, Kharms, Richard Broderick license = GPLv3 description = Make difficult regular expressions easy! Python fork based on of the awesome VerbalExpressions repo - https://github.com/jehna/VerbalExpressions diff --git a/verbex/__init__.py b/verbex/__init__.py index fa3bafc..566fb9a 100644 --- a/verbex/__init__.py +++ b/verbex/__init__.py @@ -1,3 +1,7 @@ +import importlib.metadata + from .verbex import CharClass as CharClass from .verbex import SpecialChar as SpecialChar from .verbex import Verbex as Verbex + +__version__ = importlib.metadata.version("verbex") From 2af09a04b35e2eae6ad9569a5ae6c118a9eb2c2c Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:25:40 -0400 Subject: [PATCH 52/90] added auto doc generate pre-commit hook --- .pre-commit-config.yaml | 1 + docs/index.html | 7 - docs/search.js | 46 - docs/verbex.html | 238 --- docs/verbex/verbex.html | 3343 --------------------------------------- 5 files changed, 1 insertion(+), 3634 deletions(-) delete mode 100644 docs/index.html delete mode 100644 docs/search.js delete mode 100644 docs/verbex.html delete mode 100644 docs/verbex/verbex.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fdf4b5d..1f73439 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -213,3 +213,4 @@ repos: pass_filenames: false additional_dependencies: - beartype + stages: [post-commit] diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 97569ec..0000000 --- a/docs/index.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/docs/search.js b/docs/search.js deleted file mode 100644 index 87f83cf..0000000 --- a/docs/search.js +++ /dev/null @@ -1,46 +0,0 @@ -window.pdocSearch = (function(){ -/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, {"fullname": "verbex.verbex", "modulename": "verbex.verbex", "type": "module", "doc": "

Generate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; - - // mirrored in build-search-index.js (part 1) - // Also split on html tags. this is a cheap heuristic, but good enough. - elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); - - let searchIndex; - if (docs._isPrebuiltIndex) { - console.info("using precompiled search index"); - searchIndex = elasticlunr.Index.load(docs); - } else { - console.time("building search index"); - // mirrored in build-search-index.js (part 2) - searchIndex = elasticlunr(function () { - this.pipeline.remove(elasticlunr.stemmer); - this.pipeline.remove(elasticlunr.stopWordFilter); - this.addField("qualname"); - this.addField("fullname"); - this.addField("annotation"); - this.addField("default_value"); - this.addField("signature"); - this.addField("bases"); - this.addField("doc"); - this.setRef("fullname"); - }); - for (let doc of docs) { - searchIndex.addDoc(doc); - } - console.timeEnd("building search index"); - } - - return (term) => searchIndex.search(term, { - fields: { - qualname: {boost: 4}, - fullname: {boost: 2}, - annotation: {boost: 2}, - default_value: {boost: 2}, - signature: {boost: 2}, - bases: {boost: 2}, - doc: {boost: 1}, - }, - expand: true - }); -})(); \ No newline at end of file diff --git a/docs/verbex.html b/docs/verbex.html deleted file mode 100644 index 189cab6..0000000 --- a/docs/verbex.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - verbex API documentation - - - - - - - - - -
-
-

-verbex

- - -
- View Source -
0import importlib.metadata
-1
-2from .verbex import CharClass as CharClass
-3from .verbex import SpecialChar as SpecialChar
-4from .verbex import Verbex as Verbex
-5
-6__version__ = importlib.metadata.version("verbex")
-
- -
- -
-
- - \ No newline at end of file diff --git a/docs/verbex/verbex.html b/docs/verbex/verbex.html deleted file mode 100644 index 48f01fc..0000000 --- a/docs/verbex/verbex.html +++ /dev/null @@ -1,3343 +0,0 @@ - - - - - - - verbex.verbex API documentation - - - - - - - - - -
-
-

-verbex.verbex

- -

Generate regular expressions from an easier fluent verbal form.

-
- -
- View Source -
  0"""Generate regular expressions from an easier fluent verbal form."""
-  1from __future__ import annotations
-  2
-  3import re
-  4from enum import Enum
-  5from functools import wraps
-  6
-  7try:
-  8    from typing import (  # <--------------- if Python ≥ 3.9.0
-  9        Annotated,
- 10        ParamSpec,
- 11        Protocol,
- 12        TypeAlias,
- 13        runtime_checkable,
- 14    )
- 15except ImportError:
- 16    from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable  # type: ignore # <--- if Python < 3.9.0 # noqa E501
- 17
- 18from typing import Pattern, TypeVar
- 19
- 20from beartype import beartype  # type: ignore
- 21from beartype.typing import (  # type: ignore
- 22    Any,
- 23    Callable,
- 24    Dict,
- 25    Iterator,
- 26    List,
- 27    Optional,
- 28    Tuple,
- 29    Union,
- 30    cast,
- 31)
- 32from beartype.vale import Is  # type: ignore
- 33
- 34
- 35def _string_len_is_1(text: object) -> bool:
- 36    return isinstance(text, str) and len(text) == 1
- 37
- 38
- 39Char = Annotated[str, Is[_string_len_is_1]]
- 40
- 41
- 42P = ParamSpec("P")  # noqa VNE001
- 43R = TypeVar("R")  # noqa VNE001
- 44
- 45
- 46# work around for bug https://github.com/python/mypy/issues/12660
- 47# fixed in next version of mypy
- 48@runtime_checkable
- 49class HasIter(Protocol):
- 50    """Workaround for mypy P.args."""
- 51
- 52    def __iter__(self) -> Iterator[Any]:
- 53        """Object can be iterated.
- 54
- 55        Yields:
- 56            Next object.
- 57        """
- 58        ...
- 59
- 60
- 61# work around for bug https://github.com/python/mypy/issues/12660
- 62# fixed in next version of mypy
- 63@runtime_checkable
- 64class HasItems(Protocol):
- 65    """Workaround for mypy P.kwargs."""
- 66
- 67    def items(self) -> Tuple[str, Any]:
- 68        """Object has items method.
- 69
- 70        Returns:
- 71            The dict of items.
- 72        """
- 73        ...
- 74
- 75
- 76class EscapedText(str):
- 77    """Text that has been escaped for regex.
- 78
- 79    Arguments:
- 80        str -- Extend the string class.
- 81    """
- 82
- 83    def __new__(cls, value: str) -> EscapedText:
- 84        """Return a escaped regex string.
- 85
- 86        Arguments:
- 87            value -- the string to escape
- 88
- 89        Returns:
- 90            _description_
- 91        """
- 92        return str.__new__(cls, re.escape(value))
- 93
- 94
- 95def re_escape(func: Callable[P, R]) -> Callable[P, R]:
- 96    """Automatically escape any string parameters as EscapedText.
- 97
- 98    Arguments:
- 99        func -- The function to decorate.
-100
-101    Returns:
-102        The decorated function.
-103    """
-104
-105    @wraps(func)
-106    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-107        escaped_args: List[Any] = []
-108        escaped_kwargs: Dict[str, Any] = {}
-109        for arg in cast(HasIter, args):
-110            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-111                escaped_args.append(EscapedText(arg))
-112            else:
-113                escaped_args.append(arg)
-114        arg_k: str
-115        arg_v: Any
-116        for arg_k, arg_v in cast(HasItems, kwargs).items():
-117            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-118                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-119            else:
-120                escaped_kwargs[arg_k] = arg_v
-121        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-122
-123    return inner
-124
-125
-126class CharClass(Enum):
-127    """Enum of character classes in regex.
-128
-129    Arguments:
-130        Enum -- Extends the Enum class.
-131    """
-132
-133    DIGIT = "\\d"
-134    LETTER = "\\w"
-135    UPPERCASE_LETTER = "\\u"
-136    LOWERCASE_LETTER = "\\l"
-137    WHITESPACE = "\\s"
-138    TAB = "\\t"
-139
-140    def __str__(self) -> str:
-141        """To string method based on Enum value.
-142
-143        Returns:
-144            value of Enum
-145        """
-146        return self.value
-147
-148
-149class SpecialChar(Enum):
-150    """Enum of special charaters, shorthand.
-151
-152    Arguments:
-153        Enum -- Extends the Enum class.
-154    """
-155
-156    # does not work  / should not be used in [ ]
-157    LINEBREAK = "(\\n|(\\r\\n))"
-158    START_OF_LINE = "^"
-159    END_OF_LINE = "$"
-160    TAB = "\t"
-161
-162    def __str__(self) -> str:
-163        """To string for special chars enum.
-164
-165        Returns:
-166            Return value of enum as string.
-167        """
-168        return self.value
-169
-170
-171CharClassOrChars: TypeAlias = Union[str, CharClass]
-172EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar]
-173VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial]
-174
-175
-176def _poseur_decorator(*poseur: Any) -> Any:
-177    """Positional-only arguments runtime checker."""
-178    import functools
-179
-180    def caller(func: Callable[P, R]) -> Callable[P, R]:  # type: ignore
-181        @functools.wraps(func)
-182        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
-183            poseur_args = set(poseur).intersection(kwargs)  # type: ignore
-184            if poseur_args:
-185                raise TypeError(
-186                    "%s() got some positional-only arguments passed as keyword"
-187                    " arguments: %r" % (func.__name__, ", ".join(poseur_args)),
-188                )
-189            return func(*args, **kwargs)  # type: ignore
-190
-191        return wrapper
-192
-193    return caller
-194
-195
-196class Verbex:
-197    """
-198    VerbalExpressions class.
-199
-200    the following methods do not try to match the original js lib!
-201    """
-202
-203    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-204
-205    @re_escape
-206    @beartype
-207    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-208        """Create a Verbex object; setting any needed flags.
-209
-210        Keyword Arguments:
-211            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-212        """
-213        # self._parts: List[str] = [text]
-214        self._parts: List[str] = []
-215        self._modifiers = modifiers
-216
-217    @property
-218    def modifiers(self) -> re.RegexFlag:
-219        """Return the modifiers for this Verbex object.
-220
-221        Returns:
-222            The modifiers applied to this object.
-223        """
-224        return self._modifiers
-225
-226    def __str__(self) -> str:
-227        """Return regex string representation."""
-228        return "".join(self._parts)
-229
-230    @beartype
-231    def _add(self, value: Union[str, List[str]]) -> Verbex:
-232        """
-233        Append a transformed value to internal expression to be compiled.
-234
-235        As possible, this method should be "private".
-236        """
-237        if isinstance(value, list):
-238            self._parts.extend(value)
-239        else:
-240            self._parts.append(value)
-241        return self
-242
-243    def regex(self) -> Pattern[str]:
-244        """Get a regular expression object."""
-245        return re.compile(
-246            str(self),
-247            self._modifiers,
-248        )
-249
-250    # allow VerbexEscapedCharClassOrSpecial
-251
-252    @re_escape
-253    @beartype
-254    def _capture_group_with_name(
-255        self,
-256        name: str,
-257        text: VerbexEscapedCharClassOrSpecial,
-258    ) -> Verbex:
-259        return self._add(f"(?<{name}>{str(text)})")
-260
-261    @re_escape
-262    @beartype
-263    def _capture_group_without_name(
-264        self,
-265        text: VerbexEscapedCharClassOrSpecial,
-266    ) -> Verbex:
-267        return self._add(f"({str(text)})")
-268
-269    @re_escape
-270    @beartype
-271    @_poseur_decorator("self")
-272    def capture_group(
-273        self,
-274        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-275        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-276    ) -> Verbex:
-277        """Create a capture group.
-278
-279        Name is optional if not specified then the first argument is the text.
-280
-281        Keyword Arguments:
-282            name_or_text -- The name of the group / text to search for (default: {None})
-283            text -- The text to search for (default: {None})
-284
-285        Raises:
-286            ValueError: If name is specified then text must be as well.
-287
-288        Returns:
-289            Verbex with added capture group.
-290        """
-291        if name_or_text is not None:
-292            if text is None:
-293                _text = name_or_text
-294                return self._capture_group_without_name(_text)
-295            if isinstance(name_or_text, str):
-296                return self._capture_group_with_name(name_or_text, text)
-297        raise ValueError("text must be specified with optional name")
-298
-299    @re_escape
-300    @beartype
-301    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-302        """`or` is a python keyword so we use `OR` instead.
-303
-304        Arguments:
-305            text -- Text to find or a Verbex object.
-306
-307        Returns:
-308            Modified Verbex object.
-309        """
-310        return self._add("|").find(text)
-311
-312    @re_escape
-313    @beartype
-314    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-315        """Find the text or Verbex object zero or more times.
-316
-317        Arguments:
-318            text -- The text / Verbex object to look for.
-319
-320        Returns:
-321            Modified Verbex object.
-322        """
-323        return self._add(f"(?:{str(text)})*")
-324
-325    @re_escape
-326    @beartype
-327    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-328        """Find the text or Verbex object one or more times.
-329
-330        Arguments:
-331            text -- The text / Verbex object to look for.
-332
-333        Returns:
-334            Modified Verbex object.
-335        """
-336        return self._add(f"(?:{str(text)})+")
-337
-338    @re_escape
-339    @beartype
-340    def n_times(
-341        self,
-342        text: VerbexEscapedCharClassOrSpecial,
-343        n: int,  # noqa: VNE001
-344    ) -> Verbex:
-345        """Find the text or Verbex object n or more times.
-346
-347        Arguments:
-348            text -- The text / Verbex object to look for.
-349
-350        Returns:
-351            Modified Verbex object.
-352        """
-353        return self._add(f"(?:{str(text)}){{{n}}}")
-354
-355    @re_escape
-356    @beartype
-357    def n_times_or_more(
-358        self,
-359        text: VerbexEscapedCharClassOrSpecial,
-360        n: int,  # noqa: VNE001
-361    ) -> Verbex:
-362        """Find the text or Verbex object at least n times.
-363
-364        Arguments:
-365            text -- The text / Verbex object to look for.
-366
-367        Returns:
-368            Modified Verbex object.
-369        """
-370        return self._add(f"(?:{str(text)}){{{n},}}")
-371
-372    @re_escape
-373    @beartype
-374    def n_to_m_times(
-375        self,
-376        text: VerbexEscapedCharClassOrSpecial,
-377        n: int,  # noqa: VNE001
-378        m: int,  # noqa: VNE001
-379    ) -> Verbex:
-380        """Find the text or Verbex object between n and m times.
-381
-382        Arguments:
-383            text -- The text / Verbex object to look for.
-384
-385        Returns:
-386            Modified Verbex object.
-387        """
-388        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-389
-390    @re_escape
-391    @beartype
-392    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-393        """Possibly find the text / Verbex object.
-394
-395        Arguments:
-396            text -- The text / Verbex object to possibly find.
-397
-398        Returns:
-399            Modified Verbex object.
-400        """
-401        return self._add(f"(?:{str(text)})?")
-402
-403    @re_escape
-404    @beartype
-405    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-406        """Find the text or Verbex object.
-407
-408        Arguments:
-409            text -- The text / Verbex object to look for.
-410
-411        Returns:
-412            Modified Verbex object.
-413        """
-414        return self._add(str(text))
-415
-416    @re_escape
-417    @beartype
-418    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-419        """Synonym for find.
-420
-421        Arguments:
-422            text -- The text / Verbex object to look for.
-423
-424        Returns:
-425            Modified Verbex object.
-426        """
-427        return self.find(text)
-428
-429    @re_escape
-430    @beartype
-431    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-432        """Match if string is followed by text.
-433
-434        Positive lookahead
-435
-436        Returns:
-437            Modified Verbex object.
-438        """
-439        return self._add(f"(?={text})")
-440
-441    @re_escape
-442    @beartype
-443    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-444        """Match if string is not followed by text.
-445
-446        Negative lookahead
-447
-448        Returns:
-449            Modified Verbex object.
-450        """
-451        return self._add(f"(?!{text})")
-452
-453    @re_escape
-454    @beartype
-455    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-456        """Match if string is not preceded by text.
-457
-458        Positive lookbehind
-459
-460        Returns:
-461            Modified Verbex object.
-462        """
-463        return self._add(f"(?<={text})")
-464
-465    @re_escape
-466    @beartype
-467    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-468        """Match if string is not preceded by text.
-469
-470        Negative Lookbehind
-471
-472        Returns:
-473            Modified Verbex object.
-474        """
-475        return self._add(f"(?<!{text})")
-476
-477    # only allow CharclassOrChars
-478
-479    @re_escape
-480    @beartype
-481    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-482        """Find anything in this group of chars or char class.
-483
-484        Arguments:
-485            text -- The characters to look for.
-486
-487        Returns:
-488            Modified Verbex object.
-489        """
-490        return self._add(f"(?:[{chargroup}])")
-491
-492    @re_escape
-493    @beartype
-494    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-495        """Find anything but this group of chars or char class.
-496
-497        Arguments:
-498            text -- The characters to not look for.
-499
-500        Returns:
-501            Modified Verbex object.
-502        """
-503        return self._add(f"(?:[^{text}])")
-504
-505    @re_escape
-506    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-507        """Find anything one or more times but this group of chars or char class.
-508
-509        Arguments:
-510            text -- The characters to not look for.
-511
-512        Returns:
-513            Modified Verbex object.
-514        """
-515        return self._add(f"[^{chargroup}]+")
-516
-517    # no text input
-518
-519    def start_of_line(self) -> Verbex:
-520        """Find the start of the line.
-521
-522        Returns:
-523            Modified Verbex object.
-524        """
-525        return self.find(SpecialChar.START_OF_LINE)
-526
-527    def end_of_line(self) -> Verbex:
-528        """Find the end of the line.
-529
-530        Returns:
-531            Modified Verbex object.
-532        """
-533        return self.find(SpecialChar.END_OF_LINE)
-534
-535    def line_break(self) -> Verbex:
-536        """Find a line break.
-537
-538        Returns:
-539            Modified Verbex object.
-540        """
-541        return self.find(SpecialChar.LINEBREAK)
-542
-543    def tab(self) -> Verbex:
-544        """Find a tab.
-545
-546        Returns:
-547            Modified Verbex object.
-548        """
-549        return self.find(SpecialChar.TAB)
-550
-551    def anything(self) -> Verbex:
-552        """Find anything one or more time.
-553
-554        Returns:
-555            Modified Verbex object.
-556        """
-557        return self._add(".+")
-558
-559    def as_few(self) -> Verbex:
-560        """Modify previous search to not be greedy.
-561
-562        Returns:
-563            Modified Verbex object.
-564        """
-565        return self._add("?")
-566
-567    @beartype
-568    def number_range(self, start: int, end: int) -> Verbex:
-569        """Generate a range of numbers.
-570
-571        Arguments:
-572            start -- Start of the range
-573            end -- End of the range
-574
-575        Returns:
-576            Modified Verbex object.
-577        """
-578        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-579
-580    @beartype
-581    def letter_range(self, start: Char, end: Char) -> Verbex:
-582        """Generate a range of letters.
-583
-584        Arguments:
-585            start -- Start of the range
-586            end -- End of the range
-587
-588        Returns:
-589            Modified Verbex object.
-590        """
-591        return self._add(f"[{start}-{end}]")
-592
-593    def word(self) -> Verbex:
-594        """Find a word on word boundary.
-595
-596        Returns:
-597            Modified Verbex object.
-598        """
-599        return self._add("(\\b\\w+\\b)")
-600
-601    # # --------------- modifiers ------------------------
-602
-603    def with_any_case(self) -> Verbex:
-604        """Modify Verbex object to be case insensitive.
-605
-606        Returns:
-607            Modified Verbex object.
-608        """
-609        self._modifiers |= re.IGNORECASE
-610        return self
-611
-612    def search_by_line(self) -> Verbex:
-613        """Search each line, ^ and $ match begining and end of line respectively.
-614
-615        Returns:
-616            Modified Verbex object.
-617        """
-618        self._modifiers |= re.MULTILINE
-619        return self
-620
-621    def with_ascii(self) -> Verbex:
-622        """Match ascii instead of unicode.
-623
-624        Returns:
-625            Modified Verbex object.
-626        """
-627        self._modifiers |= re.ASCII
-628        return self
-629
-630
-631# left over notes from original version
-632# def __getattr__(self, attr):
-633#     """ any other function will be sent to the regex object """
-634#     regex = self.regex()
-635#     return getattr(regex, attr)
-636
-637# def replace(self, string, repl):
-638#     return self.sub(repl, string)
-639
-640
-641if __name__ == "__main__":
-642    pass
-
- -
- -
-
-
#   - - P = ~P -
- - - - -
-
-
- #   - -
@runtime_checkable
- - class - HasIter(typing.Protocol): -
- -
- View Source -
49@runtime_checkable
-50class HasIter(Protocol):
-51    """Workaround for mypy P.args."""
-52
-53    def __iter__(self) -> Iterator[Any]:
-54        """Object can be iterated.
-55
-56        Yields:
-57            Next object.
-58        """
-59        ...
-
- -
- -

Workaround for mypy P.args.

-
- - -
-
#   - - - HasIter(*args, **kwargs) -
- -
- View Source -
1429def _no_init_or_replace_init(self, *args, **kwargs):
-1430    cls = type(self)
-1431
-1432    if cls._is_protocol:
-1433        raise TypeError('Protocols cannot be instantiated')
-1434
-1435    # Already using a custom `__init__`. No need to calculate correct
-1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
-1437    if cls.__init__ is not _no_init_or_replace_init:
-1438        return
-1439
-1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
-1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
-1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
-1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
-1444    # instantiation of the protocol subclass will thus use the new
-1445    # `__init__` and no longer call `_no_init_or_replace_init`.
-1446    for base in cls.__mro__:
-1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
-1448        if init is not _no_init_or_replace_init:
-1449            cls.__init__ = init
-1450            break
-1451    else:
-1452        # should not happen
-1453        cls.__init__ = object.__init__
-1454
-1455    cls.__init__(self, *args, **kwargs)
-
- -
- - - -
-
-
-
- #   - -
@runtime_checkable
- - class - HasItems(typing.Protocol): -
- -
- View Source -
64@runtime_checkable
-65class HasItems(Protocol):
-66    """Workaround for mypy P.kwargs."""
-67
-68    def items(self) -> Tuple[str, Any]:
-69        """Object has items method.
-70
-71        Returns:
-72            The dict of items.
-73        """
-74        ...
-
- -
- -

Workaround for mypy P.kwargs.

-
- - -
-
#   - - - HasItems(*args, **kwargs) -
- -
- View Source -
1429def _no_init_or_replace_init(self, *args, **kwargs):
-1430    cls = type(self)
-1431
-1432    if cls._is_protocol:
-1433        raise TypeError('Protocols cannot be instantiated')
-1434
-1435    # Already using a custom `__init__`. No need to calculate correct
-1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
-1437    if cls.__init__ is not _no_init_or_replace_init:
-1438        return
-1439
-1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
-1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
-1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
-1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
-1444    # instantiation of the protocol subclass will thus use the new
-1445    # `__init__` and no longer call `_no_init_or_replace_init`.
-1446    for base in cls.__mro__:
-1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
-1448        if init is not _no_init_or_replace_init:
-1449            cls.__init__ = init
-1450            break
-1451    else:
-1452        # should not happen
-1453        cls.__init__ = object.__init__
-1454
-1455    cls.__init__(self, *args, **kwargs)
-
- -
- - - -
-
-
#   - - - def - items(self) -> tuple[str, typing.Any]: -
- -
- View Source -
68    def items(self) -> Tuple[str, Any]:
-69        """Object has items method.
-70
-71        Returns:
-72            The dict of items.
-73        """
-74        ...
-
- -
- -

Object has items method.

- -

Returns: - The dict of items.

-
- - -
-
-
-
- #   - - - class - EscapedText(builtins.str): -
- -
- View Source -
77class EscapedText(str):
-78    """Text that has been escaped for regex.
-79
-80    Arguments:
-81        str -- Extend the string class.
-82    """
-83
-84    def __new__(cls, value: str) -> EscapedText:
-85        """Return a escaped regex string.
-86
-87        Arguments:
-88            value -- the string to escape
-89
-90        Returns:
-91            _description_
-92        """
-93        return str.__new__(cls, re.escape(value))
-
- -
- -

Text that has been escaped for regex.

- -

Arguments: - str -- Extend the string class.

-
- - -
-
#   - - - EscapedText(value: str) -
- -
- View Source -
84    def __new__(cls, value: str) -> EscapedText:
-85        """Return a escaped regex string.
-86
-87        Arguments:
-88            value -- the string to escape
-89
-90        Returns:
-91            _description_
-92        """
-93        return str.__new__(cls, re.escape(value))
-
- -
- -

Return a escaped regex string.

- -

Arguments: - value -- the string to escape

- -

Returns: - _description_

-
- - -
-
-
Inherited Members
-
-
builtins.str
-
encode
-
replace
-
split
-
rsplit
-
join
-
capitalize
-
casefold
-
title
-
center
-
count
-
expandtabs
-
find
-
partition
-
index
-
ljust
-
lower
-
lstrip
-
rfind
-
rindex
-
rjust
-
rstrip
-
rpartition
-
splitlines
-
strip
-
swapcase
-
translate
-
upper
-
startswith
-
endswith
-
removeprefix
-
removesuffix
-
isascii
-
islower
-
isupper
-
istitle
-
isspace
-
isdecimal
-
isdigit
-
isnumeric
-
isalpha
-
isalnum
-
isidentifier
-
isprintable
-
zfill
-
format
-
format_map
-
maketrans
- -
-
-
-
-
-
#   - - - def - re_escape( - func: collections.abc.Callable[~P, ~R] -) -> collections.abc.Callable[~P, ~R]: -
- -
- View Source -
 96def re_escape(func: Callable[P, R]) -> Callable[P, R]:
- 97    """Automatically escape any string parameters as EscapedText.
- 98
- 99    Arguments:
-100        func -- The function to decorate.
-101
-102    Returns:
-103        The decorated function.
-104    """
-105
-106    @wraps(func)
-107    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-108        escaped_args: List[Any] = []
-109        escaped_kwargs: Dict[str, Any] = {}
-110        for arg in cast(HasIter, args):
-111            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-112                escaped_args.append(EscapedText(arg))
-113            else:
-114                escaped_args.append(arg)
-115        arg_k: str
-116        arg_v: Any
-117        for arg_k, arg_v in cast(HasItems, kwargs).items():
-118            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-119                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-120            else:
-121                escaped_kwargs[arg_k] = arg_v
-122        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-123
-124    return inner
-
- -
- -

Automatically escape any string parameters as EscapedText.

- -

Arguments: - func -- The function to decorate.

- -

Returns: - The decorated function.

-
- - -
-
-
- #   - - - class - CharClass(enum.Enum): -
- -
- View Source -
127class CharClass(Enum):
-128    """Enum of character classes in regex.
-129
-130    Arguments:
-131        Enum -- Extends the Enum class.
-132    """
-133
-134    DIGIT = "\\d"
-135    LETTER = "\\w"
-136    UPPERCASE_LETTER = "\\u"
-137    LOWERCASE_LETTER = "\\l"
-138    WHITESPACE = "\\s"
-139    TAB = "\\t"
-140
-141    def __str__(self) -> str:
-142        """To string method based on Enum value.
-143
-144        Returns:
-145            value of Enum
-146        """
-147        return self.value
-
- -
- -

Enum of character classes in regex.

- -

Arguments: - Enum -- Extends the Enum class.

-
- - -
-
#   - - DIGIT = <CharClass.DIGIT: '\\d'> -
- - - - -
-
-
#   - - LETTER = <CharClass.LETTER: '\\w'> -
- - - - -
-
-
#   - - UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'> -
- - - - -
-
-
#   - - LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'> -
- - - - -
-
-
#   - - WHITESPACE = <CharClass.WHITESPACE: '\\s'> -
- - - - -
-
-
#   - - TAB = <CharClass.TAB: '\\t'> -
- - - - -
-
-
Inherited Members
-
-
enum.Enum
-
name
-
value
- -
-
-
-
-
-
- #   - - - class - SpecialChar(enum.Enum): -
- -
- View Source -
150class SpecialChar(Enum):
-151    """Enum of special charaters, shorthand.
-152
-153    Arguments:
-154        Enum -- Extends the Enum class.
-155    """
-156
-157    # does not work  / should not be used in [ ]
-158    LINEBREAK = "(\\n|(\\r\\n))"
-159    START_OF_LINE = "^"
-160    END_OF_LINE = "$"
-161    TAB = "\t"
-162
-163    def __str__(self) -> str:
-164        """To string for special chars enum.
-165
-166        Returns:
-167            Return value of enum as string.
-168        """
-169        return self.value
-
- -
- -

Enum of special charaters, shorthand.

- -

Arguments: - Enum -- Extends the Enum class.

-
- - -
-
#   - - LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'> -
- - - - -
-
-
#   - - START_OF_LINE = <SpecialChar.START_OF_LINE: '^'> -
- - - - -
-
-
#   - - END_OF_LINE = <SpecialChar.END_OF_LINE: '$'> -
- - - - -
-
-
#   - - TAB = <SpecialChar.TAB: '\t'> -
- - - - -
-
-
Inherited Members
-
-
enum.Enum
-
name
-
value
- -
-
-
-
-
-
#   - - CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass] -
- - - - -
-
-
#   - - EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -
- - - - -
-
-
#   - - VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -
- - - - -
-
-
- #   - - - class - Verbex: -
- -
- View Source -
197class Verbex:
-198    """
-199    VerbalExpressions class.
-200
-201    the following methods do not try to match the original js lib!
-202    """
-203
-204    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-205
-206    @re_escape
-207    @beartype
-208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-209        """Create a Verbex object; setting any needed flags.
-210
-211        Keyword Arguments:
-212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-213        """
-214        # self._parts: List[str] = [text]
-215        self._parts: List[str] = []
-216        self._modifiers = modifiers
-217
-218    @property
-219    def modifiers(self) -> re.RegexFlag:
-220        """Return the modifiers for this Verbex object.
-221
-222        Returns:
-223            The modifiers applied to this object.
-224        """
-225        return self._modifiers
-226
-227    def __str__(self) -> str:
-228        """Return regex string representation."""
-229        return "".join(self._parts)
-230
-231    @beartype
-232    def _add(self, value: Union[str, List[str]]) -> Verbex:
-233        """
-234        Append a transformed value to internal expression to be compiled.
-235
-236        As possible, this method should be "private".
-237        """
-238        if isinstance(value, list):
-239            self._parts.extend(value)
-240        else:
-241            self._parts.append(value)
-242        return self
-243
-244    def regex(self) -> Pattern[str]:
-245        """Get a regular expression object."""
-246        return re.compile(
-247            str(self),
-248            self._modifiers,
-249        )
-250
-251    # allow VerbexEscapedCharClassOrSpecial
-252
-253    @re_escape
-254    @beartype
-255    def _capture_group_with_name(
-256        self,
-257        name: str,
-258        text: VerbexEscapedCharClassOrSpecial,
-259    ) -> Verbex:
-260        return self._add(f"(?<{name}>{str(text)})")
-261
-262    @re_escape
-263    @beartype
-264    def _capture_group_without_name(
-265        self,
-266        text: VerbexEscapedCharClassOrSpecial,
-267    ) -> Verbex:
-268        return self._add(f"({str(text)})")
-269
-270    @re_escape
-271    @beartype
-272    @_poseur_decorator("self")
-273    def capture_group(
-274        self,
-275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-277    ) -> Verbex:
-278        """Create a capture group.
-279
-280        Name is optional if not specified then the first argument is the text.
-281
-282        Keyword Arguments:
-283            name_or_text -- The name of the group / text to search for (default: {None})
-284            text -- The text to search for (default: {None})
-285
-286        Raises:
-287            ValueError: If name is specified then text must be as well.
-288
-289        Returns:
-290            Verbex with added capture group.
-291        """
-292        if name_or_text is not None:
-293            if text is None:
-294                _text = name_or_text
-295                return self._capture_group_without_name(_text)
-296            if isinstance(name_or_text, str):
-297                return self._capture_group_with_name(name_or_text, text)
-298        raise ValueError("text must be specified with optional name")
-299
-300    @re_escape
-301    @beartype
-302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-303        """`or` is a python keyword so we use `OR` instead.
-304
-305        Arguments:
-306            text -- Text to find or a Verbex object.
-307
-308        Returns:
-309            Modified Verbex object.
-310        """
-311        return self._add("|").find(text)
-312
-313    @re_escape
-314    @beartype
-315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-316        """Find the text or Verbex object zero or more times.
-317
-318        Arguments:
-319            text -- The text / Verbex object to look for.
-320
-321        Returns:
-322            Modified Verbex object.
-323        """
-324        return self._add(f"(?:{str(text)})*")
-325
-326    @re_escape
-327    @beartype
-328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-329        """Find the text or Verbex object one or more times.
-330
-331        Arguments:
-332            text -- The text / Verbex object to look for.
-333
-334        Returns:
-335            Modified Verbex object.
-336        """
-337        return self._add(f"(?:{str(text)})+")
-338
-339    @re_escape
-340    @beartype
-341    def n_times(
-342        self,
-343        text: VerbexEscapedCharClassOrSpecial,
-344        n: int,  # noqa: VNE001
-345    ) -> Verbex:
-346        """Find the text or Verbex object n or more times.
-347
-348        Arguments:
-349            text -- The text / Verbex object to look for.
-350
-351        Returns:
-352            Modified Verbex object.
-353        """
-354        return self._add(f"(?:{str(text)}){{{n}}}")
-355
-356    @re_escape
-357    @beartype
-358    def n_times_or_more(
-359        self,
-360        text: VerbexEscapedCharClassOrSpecial,
-361        n: int,  # noqa: VNE001
-362    ) -> Verbex:
-363        """Find the text or Verbex object at least n times.
-364
-365        Arguments:
-366            text -- The text / Verbex object to look for.
-367
-368        Returns:
-369            Modified Verbex object.
-370        """
-371        return self._add(f"(?:{str(text)}){{{n},}}")
-372
-373    @re_escape
-374    @beartype
-375    def n_to_m_times(
-376        self,
-377        text: VerbexEscapedCharClassOrSpecial,
-378        n: int,  # noqa: VNE001
-379        m: int,  # noqa: VNE001
-380    ) -> Verbex:
-381        """Find the text or Verbex object between n and m times.
-382
-383        Arguments:
-384            text -- The text / Verbex object to look for.
-385
-386        Returns:
-387            Modified Verbex object.
-388        """
-389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-390
-391    @re_escape
-392    @beartype
-393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-394        """Possibly find the text / Verbex object.
-395
-396        Arguments:
-397            text -- The text / Verbex object to possibly find.
-398
-399        Returns:
-400            Modified Verbex object.
-401        """
-402        return self._add(f"(?:{str(text)})?")
-403
-404    @re_escape
-405    @beartype
-406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-407        """Find the text or Verbex object.
-408
-409        Arguments:
-410            text -- The text / Verbex object to look for.
-411
-412        Returns:
-413            Modified Verbex object.
-414        """
-415        return self._add(str(text))
-416
-417    @re_escape
-418    @beartype
-419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-420        """Synonym for find.
-421
-422        Arguments:
-423            text -- The text / Verbex object to look for.
-424
-425        Returns:
-426            Modified Verbex object.
-427        """
-428        return self.find(text)
-429
-430    @re_escape
-431    @beartype
-432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-433        """Match if string is followed by text.
-434
-435        Positive lookahead
-436
-437        Returns:
-438            Modified Verbex object.
-439        """
-440        return self._add(f"(?={text})")
-441
-442    @re_escape
-443    @beartype
-444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-445        """Match if string is not followed by text.
-446
-447        Negative lookahead
-448
-449        Returns:
-450            Modified Verbex object.
-451        """
-452        return self._add(f"(?!{text})")
-453
-454    @re_escape
-455    @beartype
-456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-457        """Match if string is not preceded by text.
-458
-459        Positive lookbehind
-460
-461        Returns:
-462            Modified Verbex object.
-463        """
-464        return self._add(f"(?<={text})")
-465
-466    @re_escape
-467    @beartype
-468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-469        """Match if string is not preceded by text.
-470
-471        Negative Lookbehind
-472
-473        Returns:
-474            Modified Verbex object.
-475        """
-476        return self._add(f"(?<!{text})")
-477
-478    # only allow CharclassOrChars
-479
-480    @re_escape
-481    @beartype
-482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-483        """Find anything in this group of chars or char class.
-484
-485        Arguments:
-486            text -- The characters to look for.
-487
-488        Returns:
-489            Modified Verbex object.
-490        """
-491        return self._add(f"(?:[{chargroup}])")
-492
-493    @re_escape
-494    @beartype
-495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-496        """Find anything but this group of chars or char class.
-497
-498        Arguments:
-499            text -- The characters to not look for.
-500
-501        Returns:
-502            Modified Verbex object.
-503        """
-504        return self._add(f"(?:[^{text}])")
-505
-506    @re_escape
-507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-508        """Find anything one or more times but this group of chars or char class.
-509
-510        Arguments:
-511            text -- The characters to not look for.
-512
-513        Returns:
-514            Modified Verbex object.
-515        """
-516        return self._add(f"[^{chargroup}]+")
-517
-518    # no text input
-519
-520    def start_of_line(self) -> Verbex:
-521        """Find the start of the line.
-522
-523        Returns:
-524            Modified Verbex object.
-525        """
-526        return self.find(SpecialChar.START_OF_LINE)
-527
-528    def end_of_line(self) -> Verbex:
-529        """Find the end of the line.
-530
-531        Returns:
-532            Modified Verbex object.
-533        """
-534        return self.find(SpecialChar.END_OF_LINE)
-535
-536    def line_break(self) -> Verbex:
-537        """Find a line break.
-538
-539        Returns:
-540            Modified Verbex object.
-541        """
-542        return self.find(SpecialChar.LINEBREAK)
-543
-544    def tab(self) -> Verbex:
-545        """Find a tab.
-546
-547        Returns:
-548            Modified Verbex object.
-549        """
-550        return self.find(SpecialChar.TAB)
-551
-552    def anything(self) -> Verbex:
-553        """Find anything one or more time.
-554
-555        Returns:
-556            Modified Verbex object.
-557        """
-558        return self._add(".+")
-559
-560    def as_few(self) -> Verbex:
-561        """Modify previous search to not be greedy.
-562
-563        Returns:
-564            Modified Verbex object.
-565        """
-566        return self._add("?")
-567
-568    @beartype
-569    def number_range(self, start: int, end: int) -> Verbex:
-570        """Generate a range of numbers.
-571
-572        Arguments:
-573            start -- Start of the range
-574            end -- End of the range
-575
-576        Returns:
-577            Modified Verbex object.
-578        """
-579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-580
-581    @beartype
-582    def letter_range(self, start: Char, end: Char) -> Verbex:
-583        """Generate a range of letters.
-584
-585        Arguments:
-586            start -- Start of the range
-587            end -- End of the range
-588
-589        Returns:
-590            Modified Verbex object.
-591        """
-592        return self._add(f"[{start}-{end}]")
-593
-594    def word(self) -> Verbex:
-595        """Find a word on word boundary.
-596
-597        Returns:
-598            Modified Verbex object.
-599        """
-600        return self._add("(\\b\\w+\\b)")
-601
-602    # # --------------- modifiers ------------------------
-603
-604    def with_any_case(self) -> Verbex:
-605        """Modify Verbex object to be case insensitive.
-606
-607        Returns:
-608            Modified Verbex object.
-609        """
-610        self._modifiers |= re.IGNORECASE
-611        return self
-612
-613    def search_by_line(self) -> Verbex:
-614        """Search each line, ^ and $ match begining and end of line respectively.
-615
-616        Returns:
-617            Modified Verbex object.
-618        """
-619        self._modifiers |= re.MULTILINE
-620        return self
-621
-622    def with_ascii(self) -> Verbex:
-623        """Match ascii instead of unicode.
-624
-625        Returns:
-626            Modified Verbex object.
-627        """
-628        self._modifiers |= re.ASCII
-629        return self
-
- -
- -

VerbalExpressions class.

- -

the following methods do not try to match the original js lib!

-
- - -
-
#   - -
@re_escape
-
@beartype
- - Verbex(modifiers: re.RegexFlag = ) -
- -
- View Source -
206    @re_escape
-207    @beartype
-208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-209        """Create a Verbex object; setting any needed flags.
-210
-211        Keyword Arguments:
-212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-213        """
-214        # self._parts: List[str] = [text]
-215        self._parts: List[str] = []
-216        self._modifiers = modifiers
-
- -
- -

Create a Verbex object; setting any needed flags.

- -

Keyword Arguments: - modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

-
- - -
-
-
#   - - EMPTY_REGEX_FLAG = -
- - - - -
-
-
#   - - modifiers: re.RegexFlag -
- - -

Return the modifiers for this Verbex object.

- -

Returns: - The modifiers applied to this object.

-
- - -
-
-
#   - - - def - regex(self) -> Pattern[str]: -
- -
- View Source -
244    def regex(self) -> Pattern[str]:
-245        """Get a regular expression object."""
-246        return re.compile(
-247            str(self),
-248            self._modifiers,
-249        )
-
- -
- -

Get a regular expression object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - capture_group( - self, - name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None -) -> verbex.verbex.Verbex: -
- -
- View Source -
270    @re_escape
-271    @beartype
-272    @_poseur_decorator("self")
-273    def capture_group(
-274        self,
-275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-277    ) -> Verbex:
-278        """Create a capture group.
-279
-280        Name is optional if not specified then the first argument is the text.
-281
-282        Keyword Arguments:
-283            name_or_text -- The name of the group / text to search for (default: {None})
-284            text -- The text to search for (default: {None})
-285
-286        Raises:
-287            ValueError: If name is specified then text must be as well.
-288
-289        Returns:
-290            Verbex with added capture group.
-291        """
-292        if name_or_text is not None:
-293            if text is None:
-294                _text = name_or_text
-295                return self._capture_group_without_name(_text)
-296            if isinstance(name_or_text, str):
-297                return self._capture_group_with_name(name_or_text, text)
-298        raise ValueError("text must be specified with optional name")
-
- -
- -

Create a capture group.

- -

Name is optional if not specified then the first argument is the text.

- -

Keyword Arguments: - name_or_text -- The name of the group / text to search for (default: {None}) - text -- The text to search for (default: {None})

- -

Raises: - ValueError: If name is specified then text must be as well.

- -

Returns: - Verbex with added capture group.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - OR( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
300    @re_escape
-301    @beartype
-302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-303        """`or` is a python keyword so we use `OR` instead.
-304
-305        Arguments:
-306            text -- Text to find or a Verbex object.
-307
-308        Returns:
-309            Modified Verbex object.
-310        """
-311        return self._add("|").find(text)
-
- -
- -

or is a python keyword so we use OR instead.

- -

Arguments: - text -- Text to find or a Verbex object.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - zero_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
313    @re_escape
-314    @beartype
-315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-316        """Find the text or Verbex object zero or more times.
-317
-318        Arguments:
-319            text -- The text / Verbex object to look for.
-320
-321        Returns:
-322            Modified Verbex object.
-323        """
-324        return self._add(f"(?:{str(text)})*")
-
- -
- -

Find the text or Verbex object zero or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - one_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
326    @re_escape
-327    @beartype
-328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-329        """Find the text or Verbex object one or more times.
-330
-331        Arguments:
-332            text -- The text / Verbex object to look for.
-333
-334        Returns:
-335            Modified Verbex object.
-336        """
-337        return self._add(f"(?:{str(text)})+")
-
- -
- -

Find the text or Verbex object one or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_times( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
339    @re_escape
-340    @beartype
-341    def n_times(
-342        self,
-343        text: VerbexEscapedCharClassOrSpecial,
-344        n: int,  # noqa: VNE001
-345    ) -> Verbex:
-346        """Find the text or Verbex object n or more times.
-347
-348        Arguments:
-349            text -- The text / Verbex object to look for.
-350
-351        Returns:
-352            Modified Verbex object.
-353        """
-354        return self._add(f"(?:{str(text)}){{{n}}}")
-
- -
- -

Find the text or Verbex object n or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_times_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
356    @re_escape
-357    @beartype
-358    def n_times_or_more(
-359        self,
-360        text: VerbexEscapedCharClassOrSpecial,
-361        n: int,  # noqa: VNE001
-362    ) -> Verbex:
-363        """Find the text or Verbex object at least n times.
-364
-365        Arguments:
-366            text -- The text / Verbex object to look for.
-367
-368        Returns:
-369            Modified Verbex object.
-370        """
-371        return self._add(f"(?:{str(text)}){{{n},}}")
-
- -
- -

Find the text or Verbex object at least n times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_to_m_times( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int, - m: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
373    @re_escape
-374    @beartype
-375    def n_to_m_times(
-376        self,
-377        text: VerbexEscapedCharClassOrSpecial,
-378        n: int,  # noqa: VNE001
-379        m: int,  # noqa: VNE001
-380    ) -> Verbex:
-381        """Find the text or Verbex object between n and m times.
-382
-383        Arguments:
-384            text -- The text / Verbex object to look for.
-385
-386        Returns:
-387            Modified Verbex object.
-388        """
-389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-
- -
- -

Find the text or Verbex object between n and m times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - maybe( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
391    @re_escape
-392    @beartype
-393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-394        """Possibly find the text / Verbex object.
-395
-396        Arguments:
-397            text -- The text / Verbex object to possibly find.
-398
-399        Returns:
-400            Modified Verbex object.
-401        """
-402        return self._add(f"(?:{str(text)})?")
-
- -
- -

Possibly find the text / Verbex object.

- -

Arguments: - text -- The text / Verbex object to possibly find.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - find( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
404    @re_escape
-405    @beartype
-406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-407        """Find the text or Verbex object.
-408
-409        Arguments:
-410            text -- The text / Verbex object to look for.
-411
-412        Returns:
-413            Modified Verbex object.
-414        """
-415        return self._add(str(text))
-
- -
- -

Find the text or Verbex object.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - then( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
417    @re_escape
-418    @beartype
-419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-420        """Synonym for find.
-421
-422        Arguments:
-423            text -- The text / Verbex object to look for.
-424
-425        Returns:
-426            Modified Verbex object.
-427        """
-428        return self.find(text)
-
- -
- -

Synonym for find.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - followed_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
430    @re_escape
-431    @beartype
-432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-433        """Match if string is followed by text.
-434
-435        Positive lookahead
-436
-437        Returns:
-438            Modified Verbex object.
-439        """
-440        return self._add(f"(?={text})")
-
- -
- -

Match if string is followed by text.

- -

Positive lookahead

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_followed_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
442    @re_escape
-443    @beartype
-444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-445        """Match if string is not followed by text.
-446
-447        Negative lookahead
-448
-449        Returns:
-450            Modified Verbex object.
-451        """
-452        return self._add(f"(?!{text})")
-
- -
- -

Match if string is not followed by text.

- -

Negative lookahead

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - preceded_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
454    @re_escape
-455    @beartype
-456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-457        """Match if string is not preceded by text.
-458
-459        Positive lookbehind
-460
-461        Returns:
-462            Modified Verbex object.
-463        """
-464        return self._add(f"(?<={text})")
-
- -
- -

Match if string is not preceded by text.

- -

Positive lookbehind

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_preceded_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
466    @re_escape
-467    @beartype
-468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-469        """Match if string is not preceded by text.
-470
-471        Negative Lookbehind
-472
-473        Returns:
-474            Modified Verbex object.
-475        """
-476        return self._add(f"(?<!{text})")
-
- -
- -

Match if string is not preceded by text.

- -

Negative Lookbehind

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - any_of( - self, - chargroup: Union[str, verbex.verbex.CharClass] -) -> verbex.verbex.Verbex: -
- -
- View Source -
480    @re_escape
-481    @beartype
-482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-483        """Find anything in this group of chars or char class.
-484
-485        Arguments:
-486            text -- The characters to look for.
-487
-488        Returns:
-489            Modified Verbex object.
-490        """
-491        return self._add(f"(?:[{chargroup}])")
-
- -
- -

Find anything in this group of chars or char class.

- -

Arguments: - text -- The characters to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_any_of( - self, - text: Union[str, verbex.verbex.CharClass] -) -> verbex.verbex.Verbex: -
- -
- View Source -
493    @re_escape
-494    @beartype
-495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-496        """Find anything but this group of chars or char class.
-497
-498        Arguments:
-499            text -- The characters to not look for.
-500
-501        Returns:
-502            Modified Verbex object.
-503        """
-504        return self._add(f"(?:[^{text}])")
-
- -
- -

Find anything but this group of chars or char class.

- -

Arguments: - text -- The characters to not look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
- - def - anything_but( - self, - chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
506    @re_escape
-507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-508        """Find anything one or more times but this group of chars or char class.
-509
-510        Arguments:
-511            text -- The characters to not look for.
-512
-513        Returns:
-514            Modified Verbex object.
-515        """
-516        return self._add(f"[^{chargroup}]+")
-
- -
- -

Find anything one or more times but this group of chars or char class.

- -

Arguments: - text -- The characters to not look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - start_of_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
520    def start_of_line(self) -> Verbex:
-521        """Find the start of the line.
-522
-523        Returns:
-524            Modified Verbex object.
-525        """
-526        return self.find(SpecialChar.START_OF_LINE)
-
- -
- -

Find the start of the line.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - end_of_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
528    def end_of_line(self) -> Verbex:
-529        """Find the end of the line.
-530
-531        Returns:
-532            Modified Verbex object.
-533        """
-534        return self.find(SpecialChar.END_OF_LINE)
-
- -
- -

Find the end of the line.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - line_break(self) -> verbex.verbex.Verbex: -
- -
- View Source -
536    def line_break(self) -> Verbex:
-537        """Find a line break.
-538
-539        Returns:
-540            Modified Verbex object.
-541        """
-542        return self.find(SpecialChar.LINEBREAK)
-
- -
- -

Find a line break.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - tab(self) -> verbex.verbex.Verbex: -
- -
- View Source -
544    def tab(self) -> Verbex:
-545        """Find a tab.
-546
-547        Returns:
-548            Modified Verbex object.
-549        """
-550        return self.find(SpecialChar.TAB)
-
- -
- -

Find a tab.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - anything(self) -> verbex.verbex.Verbex: -
- -
- View Source -
552    def anything(self) -> Verbex:
-553        """Find anything one or more time.
-554
-555        Returns:
-556            Modified Verbex object.
-557        """
-558        return self._add(".+")
-
- -
- -

Find anything one or more time.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - as_few(self) -> verbex.verbex.Verbex: -
- -
- View Source -
560    def as_few(self) -> Verbex:
-561        """Modify previous search to not be greedy.
-562
-563        Returns:
-564            Modified Verbex object.
-565        """
-566        return self._add("?")
-
- -
- -

Modify previous search to not be greedy.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@beartype
- - def - number_range(self, start: int, end: int) -> verbex.verbex.Verbex: -
- -
- View Source -
568    @beartype
-569    def number_range(self, start: int, end: int) -> Verbex:
-570        """Generate a range of numbers.
-571
-572        Arguments:
-573            start -- Start of the range
-574            end -- End of the range
-575
-576        Returns:
-577            Modified Verbex object.
-578        """
-579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-
- -
- -

Generate a range of numbers.

- -

Arguments: - start -- Start of the range - end -- End of the range

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@beartype
- - def - letter_range( - self, - start: typing.Annotated[str, Is[_string_len_is_1]], - end: typing.Annotated[str, Is[_string_len_is_1]] -) -> verbex.verbex.Verbex: -
- -
- View Source -
581    @beartype
-582    def letter_range(self, start: Char, end: Char) -> Verbex:
-583        """Generate a range of letters.
-584
-585        Arguments:
-586            start -- Start of the range
-587            end -- End of the range
-588
-589        Returns:
-590            Modified Verbex object.
-591        """
-592        return self._add(f"[{start}-{end}]")
-
- -
- -

Generate a range of letters.

- -

Arguments: - start -- Start of the range - end -- End of the range

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - word(self) -> verbex.verbex.Verbex: -
- -
- View Source -
594    def word(self) -> Verbex:
-595        """Find a word on word boundary.
-596
-597        Returns:
-598            Modified Verbex object.
-599        """
-600        return self._add("(\\b\\w+\\b)")
-
- -
- -

Find a word on word boundary.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - with_any_case(self) -> verbex.verbex.Verbex: -
- -
- View Source -
604    def with_any_case(self) -> Verbex:
-605        """Modify Verbex object to be case insensitive.
-606
-607        Returns:
-608            Modified Verbex object.
-609        """
-610        self._modifiers |= re.IGNORECASE
-611        return self
-
- -
- -

Modify Verbex object to be case insensitive.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - search_by_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
613    def search_by_line(self) -> Verbex:
-614        """Search each line, ^ and $ match begining and end of line respectively.
-615
-616        Returns:
-617            Modified Verbex object.
-618        """
-619        self._modifiers |= re.MULTILINE
-620        return self
-
- -
- -

Search each line, ^ and $ match begining and end of line respectively.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - with_ascii(self) -> verbex.verbex.Verbex: -
- -
- View Source -
622    def with_ascii(self) -> Verbex:
-623        """Match ascii instead of unicode.
-624
-625        Returns:
-626            Modified Verbex object.
-627        """
-628        self._modifiers |= re.ASCII
-629        return self
-
- -
- -

Match ascii instead of unicode.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
- - \ No newline at end of file From cb25f044d72d2519809fb94e35fcf64abea71399 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:28:42 -0400 Subject: [PATCH 53/90] added auto doc generate pre-commit hook --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1f73439..14aad4e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -203,7 +203,7 @@ repos: hooks: - id: pdoc name: "Generate Documentation" - description: 'Auto generating documentation with pdoc' + description: 'Auto generating documentation with PDOC' entry: pdoc args: [verbex, -o, docs] language: python From f7efcf5a7b0c0a21caaf6b7ee4d6c0b634d69ddd Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:31:32 -0400 Subject: [PATCH 54/90] added auto doc generate pre-commit hook --- verbex/verbex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/verbex/verbex.py b/verbex/verbex.py index c383643..d47fd4d 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -45,7 +45,7 @@ def _string_len_is_1(text: object) -> bool: # work around for bug https://github.com/python/mypy/issues/12660 -# fixed in next version of mypy +# fixed in next version of mypy. @runtime_checkable class HasIter(Protocol): """Workaround for mypy P.args.""" From 38c60681fed3b7d7c7dcc775c1d6af0a65c25e16 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:34:11 -0400 Subject: [PATCH 55/90] added auto doc generate pre-commit hook --- .pre-commit-config.yaml | 1 - docs/index.html | 7 + docs/search.js | 46 + docs/verbex.html | 238 +++ docs/verbex/verbex.html | 3343 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 3634 insertions(+), 1 deletion(-) create mode 100644 docs/index.html create mode 100644 docs/search.js create mode 100644 docs/verbex.html create mode 100644 docs/verbex/verbex.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 14aad4e..f5d3702 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -213,4 +213,3 @@ repos: pass_filenames: false additional_dependencies: - beartype - stages: [post-commit] diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..97569ec --- /dev/null +++ b/docs/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/search.js b/docs/search.js new file mode 100644 index 0000000..87f83cf --- /dev/null +++ b/docs/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, {"fullname": "verbex.verbex", "modulename": "verbex.verbex", "type": "module", "doc": "

Generate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/docs/verbex.html b/docs/verbex.html new file mode 100644 index 0000000..189cab6 --- /dev/null +++ b/docs/verbex.html @@ -0,0 +1,238 @@ + + + + + + + verbex API documentation + + + + + + + + + +
+
+

+verbex

+ + +
+ View Source +
0import importlib.metadata
+1
+2from .verbex import CharClass as CharClass
+3from .verbex import SpecialChar as SpecialChar
+4from .verbex import Verbex as Verbex
+5
+6__version__ = importlib.metadata.version("verbex")
+
+ +
+ +
+
+ + \ No newline at end of file diff --git a/docs/verbex/verbex.html b/docs/verbex/verbex.html new file mode 100644 index 0000000..59dc123 --- /dev/null +++ b/docs/verbex/verbex.html @@ -0,0 +1,3343 @@ + + + + + + + verbex.verbex API documentation + + + + + + + + + +
+
+

+verbex.verbex

+ +

Generate regular expressions from an easier fluent verbal form.

+
+ +
+ View Source +
  0"""Generate regular expressions from an easier fluent verbal form."""
+  1from __future__ import annotations
+  2
+  3import re
+  4from enum import Enum
+  5from functools import wraps
+  6
+  7try:
+  8    from typing import (  # <--------------- if Python ≥ 3.9.0
+  9        Annotated,
+ 10        ParamSpec,
+ 11        Protocol,
+ 12        TypeAlias,
+ 13        runtime_checkable,
+ 14    )
+ 15except ImportError:
+ 16    from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable  # type: ignore # <--- if Python < 3.9.0 # noqa E501
+ 17
+ 18from typing import Pattern, TypeVar
+ 19
+ 20from beartype import beartype  # type: ignore
+ 21from beartype.typing import (  # type: ignore
+ 22    Any,
+ 23    Callable,
+ 24    Dict,
+ 25    Iterator,
+ 26    List,
+ 27    Optional,
+ 28    Tuple,
+ 29    Union,
+ 30    cast,
+ 31)
+ 32from beartype.vale import Is  # type: ignore
+ 33
+ 34
+ 35def _string_len_is_1(text: object) -> bool:
+ 36    return isinstance(text, str) and len(text) == 1
+ 37
+ 38
+ 39Char = Annotated[str, Is[_string_len_is_1]]
+ 40
+ 41
+ 42P = ParamSpec("P")  # noqa VNE001
+ 43R = TypeVar("R")  # noqa VNE001
+ 44
+ 45
+ 46# work around for bug https://github.com/python/mypy/issues/12660
+ 47# fixed in next version of mypy.
+ 48@runtime_checkable
+ 49class HasIter(Protocol):
+ 50    """Workaround for mypy P.args."""
+ 51
+ 52    def __iter__(self) -> Iterator[Any]:
+ 53        """Object can be iterated.
+ 54
+ 55        Yields:
+ 56            Next object.
+ 57        """
+ 58        ...
+ 59
+ 60
+ 61# work around for bug https://github.com/python/mypy/issues/12660
+ 62# fixed in next version of mypy
+ 63@runtime_checkable
+ 64class HasItems(Protocol):
+ 65    """Workaround for mypy P.kwargs."""
+ 66
+ 67    def items(self) -> Tuple[str, Any]:
+ 68        """Object has items method.
+ 69
+ 70        Returns:
+ 71            The dict of items.
+ 72        """
+ 73        ...
+ 74
+ 75
+ 76class EscapedText(str):
+ 77    """Text that has been escaped for regex.
+ 78
+ 79    Arguments:
+ 80        str -- Extend the string class.
+ 81    """
+ 82
+ 83    def __new__(cls, value: str) -> EscapedText:
+ 84        """Return a escaped regex string.
+ 85
+ 86        Arguments:
+ 87            value -- the string to escape
+ 88
+ 89        Returns:
+ 90            _description_
+ 91        """
+ 92        return str.__new__(cls, re.escape(value))
+ 93
+ 94
+ 95def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+ 96    """Automatically escape any string parameters as EscapedText.
+ 97
+ 98    Arguments:
+ 99        func -- The function to decorate.
+100
+101    Returns:
+102        The decorated function.
+103    """
+104
+105    @wraps(func)
+106    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+107        escaped_args: List[Any] = []
+108        escaped_kwargs: Dict[str, Any] = {}
+109        for arg in cast(HasIter, args):
+110            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+111                escaped_args.append(EscapedText(arg))
+112            else:
+113                escaped_args.append(arg)
+114        arg_k: str
+115        arg_v: Any
+116        for arg_k, arg_v in cast(HasItems, kwargs).items():
+117            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+118                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+119            else:
+120                escaped_kwargs[arg_k] = arg_v
+121        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+122
+123    return inner
+124
+125
+126class CharClass(Enum):
+127    """Enum of character classes in regex.
+128
+129    Arguments:
+130        Enum -- Extends the Enum class.
+131    """
+132
+133    DIGIT = "\\d"
+134    LETTER = "\\w"
+135    UPPERCASE_LETTER = "\\u"
+136    LOWERCASE_LETTER = "\\l"
+137    WHITESPACE = "\\s"
+138    TAB = "\\t"
+139
+140    def __str__(self) -> str:
+141        """To string method based on Enum value.
+142
+143        Returns:
+144            value of Enum
+145        """
+146        return self.value
+147
+148
+149class SpecialChar(Enum):
+150    """Enum of special charaters, shorthand.
+151
+152    Arguments:
+153        Enum -- Extends the Enum class.
+154    """
+155
+156    # does not work  / should not be used in [ ]
+157    LINEBREAK = "(\\n|(\\r\\n))"
+158    START_OF_LINE = "^"
+159    END_OF_LINE = "$"
+160    TAB = "\t"
+161
+162    def __str__(self) -> str:
+163        """To string for special chars enum.
+164
+165        Returns:
+166            Return value of enum as string.
+167        """
+168        return self.value
+169
+170
+171CharClassOrChars: TypeAlias = Union[str, CharClass]
+172EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar]
+173VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial]
+174
+175
+176def _poseur_decorator(*poseur: Any) -> Any:
+177    """Positional-only arguments runtime checker."""
+178    import functools
+179
+180    def caller(func: Callable[P, R]) -> Callable[P, R]:  # type: ignore
+181        @functools.wraps(func)
+182        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
+183            poseur_args = set(poseur).intersection(kwargs)  # type: ignore
+184            if poseur_args:
+185                raise TypeError(
+186                    "%s() got some positional-only arguments passed as keyword"
+187                    " arguments: %r" % (func.__name__, ", ".join(poseur_args)),
+188                )
+189            return func(*args, **kwargs)  # type: ignore
+190
+191        return wrapper
+192
+193    return caller
+194
+195
+196class Verbex:
+197    """
+198    VerbalExpressions class.
+199
+200    the following methods do not try to match the original js lib!
+201    """
+202
+203    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+204
+205    @re_escape
+206    @beartype
+207    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+208        """Create a Verbex object; setting any needed flags.
+209
+210        Keyword Arguments:
+211            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+212        """
+213        # self._parts: List[str] = [text]
+214        self._parts: List[str] = []
+215        self._modifiers = modifiers
+216
+217    @property
+218    def modifiers(self) -> re.RegexFlag:
+219        """Return the modifiers for this Verbex object.
+220
+221        Returns:
+222            The modifiers applied to this object.
+223        """
+224        return self._modifiers
+225
+226    def __str__(self) -> str:
+227        """Return regex string representation."""
+228        return "".join(self._parts)
+229
+230    @beartype
+231    def _add(self, value: Union[str, List[str]]) -> Verbex:
+232        """
+233        Append a transformed value to internal expression to be compiled.
+234
+235        As possible, this method should be "private".
+236        """
+237        if isinstance(value, list):
+238            self._parts.extend(value)
+239        else:
+240            self._parts.append(value)
+241        return self
+242
+243    def regex(self) -> Pattern[str]:
+244        """Get a regular expression object."""
+245        return re.compile(
+246            str(self),
+247            self._modifiers,
+248        )
+249
+250    # allow VerbexEscapedCharClassOrSpecial
+251
+252    @re_escape
+253    @beartype
+254    def _capture_group_with_name(
+255        self,
+256        name: str,
+257        text: VerbexEscapedCharClassOrSpecial,
+258    ) -> Verbex:
+259        return self._add(f"(?<{name}>{str(text)})")
+260
+261    @re_escape
+262    @beartype
+263    def _capture_group_without_name(
+264        self,
+265        text: VerbexEscapedCharClassOrSpecial,
+266    ) -> Verbex:
+267        return self._add(f"({str(text)})")
+268
+269    @re_escape
+270    @beartype
+271    @_poseur_decorator("self")
+272    def capture_group(
+273        self,
+274        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+275        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+276    ) -> Verbex:
+277        """Create a capture group.
+278
+279        Name is optional if not specified then the first argument is the text.
+280
+281        Keyword Arguments:
+282            name_or_text -- The name of the group / text to search for (default: {None})
+283            text -- The text to search for (default: {None})
+284
+285        Raises:
+286            ValueError: If name is specified then text must be as well.
+287
+288        Returns:
+289            Verbex with added capture group.
+290        """
+291        if name_or_text is not None:
+292            if text is None:
+293                _text = name_or_text
+294                return self._capture_group_without_name(_text)
+295            if isinstance(name_or_text, str):
+296                return self._capture_group_with_name(name_or_text, text)
+297        raise ValueError("text must be specified with optional name")
+298
+299    @re_escape
+300    @beartype
+301    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+302        """`or` is a python keyword so we use `OR` instead.
+303
+304        Arguments:
+305            text -- Text to find or a Verbex object.
+306
+307        Returns:
+308            Modified Verbex object.
+309        """
+310        return self._add("|").find(text)
+311
+312    @re_escape
+313    @beartype
+314    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+315        """Find the text or Verbex object zero or more times.
+316
+317        Arguments:
+318            text -- The text / Verbex object to look for.
+319
+320        Returns:
+321            Modified Verbex object.
+322        """
+323        return self._add(f"(?:{str(text)})*")
+324
+325    @re_escape
+326    @beartype
+327    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+328        """Find the text or Verbex object one or more times.
+329
+330        Arguments:
+331            text -- The text / Verbex object to look for.
+332
+333        Returns:
+334            Modified Verbex object.
+335        """
+336        return self._add(f"(?:{str(text)})+")
+337
+338    @re_escape
+339    @beartype
+340    def n_times(
+341        self,
+342        text: VerbexEscapedCharClassOrSpecial,
+343        n: int,  # noqa: VNE001
+344    ) -> Verbex:
+345        """Find the text or Verbex object n or more times.
+346
+347        Arguments:
+348            text -- The text / Verbex object to look for.
+349
+350        Returns:
+351            Modified Verbex object.
+352        """
+353        return self._add(f"(?:{str(text)}){{{n}}}")
+354
+355    @re_escape
+356    @beartype
+357    def n_times_or_more(
+358        self,
+359        text: VerbexEscapedCharClassOrSpecial,
+360        n: int,  # noqa: VNE001
+361    ) -> Verbex:
+362        """Find the text or Verbex object at least n times.
+363
+364        Arguments:
+365            text -- The text / Verbex object to look for.
+366
+367        Returns:
+368            Modified Verbex object.
+369        """
+370        return self._add(f"(?:{str(text)}){{{n},}}")
+371
+372    @re_escape
+373    @beartype
+374    def n_to_m_times(
+375        self,
+376        text: VerbexEscapedCharClassOrSpecial,
+377        n: int,  # noqa: VNE001
+378        m: int,  # noqa: VNE001
+379    ) -> Verbex:
+380        """Find the text or Verbex object between n and m times.
+381
+382        Arguments:
+383            text -- The text / Verbex object to look for.
+384
+385        Returns:
+386            Modified Verbex object.
+387        """
+388        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+389
+390    @re_escape
+391    @beartype
+392    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+393        """Possibly find the text / Verbex object.
+394
+395        Arguments:
+396            text -- The text / Verbex object to possibly find.
+397
+398        Returns:
+399            Modified Verbex object.
+400        """
+401        return self._add(f"(?:{str(text)})?")
+402
+403    @re_escape
+404    @beartype
+405    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+406        """Find the text or Verbex object.
+407
+408        Arguments:
+409            text -- The text / Verbex object to look for.
+410
+411        Returns:
+412            Modified Verbex object.
+413        """
+414        return self._add(str(text))
+415
+416    @re_escape
+417    @beartype
+418    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+419        """Synonym for find.
+420
+421        Arguments:
+422            text -- The text / Verbex object to look for.
+423
+424        Returns:
+425            Modified Verbex object.
+426        """
+427        return self.find(text)
+428
+429    @re_escape
+430    @beartype
+431    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+432        """Match if string is followed by text.
+433
+434        Positive lookahead
+435
+436        Returns:
+437            Modified Verbex object.
+438        """
+439        return self._add(f"(?={text})")
+440
+441    @re_escape
+442    @beartype
+443    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+444        """Match if string is not followed by text.
+445
+446        Negative lookahead
+447
+448        Returns:
+449            Modified Verbex object.
+450        """
+451        return self._add(f"(?!{text})")
+452
+453    @re_escape
+454    @beartype
+455    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+456        """Match if string is not preceded by text.
+457
+458        Positive lookbehind
+459
+460        Returns:
+461            Modified Verbex object.
+462        """
+463        return self._add(f"(?<={text})")
+464
+465    @re_escape
+466    @beartype
+467    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+468        """Match if string is not preceded by text.
+469
+470        Negative Lookbehind
+471
+472        Returns:
+473            Modified Verbex object.
+474        """
+475        return self._add(f"(?<!{text})")
+476
+477    # only allow CharclassOrChars
+478
+479    @re_escape
+480    @beartype
+481    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+482        """Find anything in this group of chars or char class.
+483
+484        Arguments:
+485            text -- The characters to look for.
+486
+487        Returns:
+488            Modified Verbex object.
+489        """
+490        return self._add(f"(?:[{chargroup}])")
+491
+492    @re_escape
+493    @beartype
+494    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+495        """Find anything but this group of chars or char class.
+496
+497        Arguments:
+498            text -- The characters to not look for.
+499
+500        Returns:
+501            Modified Verbex object.
+502        """
+503        return self._add(f"(?:[^{text}])")
+504
+505    @re_escape
+506    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+507        """Find anything one or more times but this group of chars or char class.
+508
+509        Arguments:
+510            text -- The characters to not look for.
+511
+512        Returns:
+513            Modified Verbex object.
+514        """
+515        return self._add(f"[^{chargroup}]+")
+516
+517    # no text input
+518
+519    def start_of_line(self) -> Verbex:
+520        """Find the start of the line.
+521
+522        Returns:
+523            Modified Verbex object.
+524        """
+525        return self.find(SpecialChar.START_OF_LINE)
+526
+527    def end_of_line(self) -> Verbex:
+528        """Find the end of the line.
+529
+530        Returns:
+531            Modified Verbex object.
+532        """
+533        return self.find(SpecialChar.END_OF_LINE)
+534
+535    def line_break(self) -> Verbex:
+536        """Find a line break.
+537
+538        Returns:
+539            Modified Verbex object.
+540        """
+541        return self.find(SpecialChar.LINEBREAK)
+542
+543    def tab(self) -> Verbex:
+544        """Find a tab.
+545
+546        Returns:
+547            Modified Verbex object.
+548        """
+549        return self.find(SpecialChar.TAB)
+550
+551    def anything(self) -> Verbex:
+552        """Find anything one or more time.
+553
+554        Returns:
+555            Modified Verbex object.
+556        """
+557        return self._add(".+")
+558
+559    def as_few(self) -> Verbex:
+560        """Modify previous search to not be greedy.
+561
+562        Returns:
+563            Modified Verbex object.
+564        """
+565        return self._add("?")
+566
+567    @beartype
+568    def number_range(self, start: int, end: int) -> Verbex:
+569        """Generate a range of numbers.
+570
+571        Arguments:
+572            start -- Start of the range
+573            end -- End of the range
+574
+575        Returns:
+576            Modified Verbex object.
+577        """
+578        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+579
+580    @beartype
+581    def letter_range(self, start: Char, end: Char) -> Verbex:
+582        """Generate a range of letters.
+583
+584        Arguments:
+585            start -- Start of the range
+586            end -- End of the range
+587
+588        Returns:
+589            Modified Verbex object.
+590        """
+591        return self._add(f"[{start}-{end}]")
+592
+593    def word(self) -> Verbex:
+594        """Find a word on word boundary.
+595
+596        Returns:
+597            Modified Verbex object.
+598        """
+599        return self._add("(\\b\\w+\\b)")
+600
+601    # # --------------- modifiers ------------------------
+602
+603    def with_any_case(self) -> Verbex:
+604        """Modify Verbex object to be case insensitive.
+605
+606        Returns:
+607            Modified Verbex object.
+608        """
+609        self._modifiers |= re.IGNORECASE
+610        return self
+611
+612    def search_by_line(self) -> Verbex:
+613        """Search each line, ^ and $ match begining and end of line respectively.
+614
+615        Returns:
+616            Modified Verbex object.
+617        """
+618        self._modifiers |= re.MULTILINE
+619        return self
+620
+621    def with_ascii(self) -> Verbex:
+622        """Match ascii instead of unicode.
+623
+624        Returns:
+625            Modified Verbex object.
+626        """
+627        self._modifiers |= re.ASCII
+628        return self
+629
+630
+631# left over notes from original version
+632# def __getattr__(self, attr):
+633#     """ any other function will be sent to the regex object """
+634#     regex = self.regex()
+635#     return getattr(regex, attr)
+636
+637# def replace(self, string, repl):
+638#     return self.sub(repl, string)
+639
+640
+641if __name__ == "__main__":
+642    pass
+
+ +
+ +
+
+
#   + + P = ~P +
+ + + + +
+
+
+ #   + +
@runtime_checkable
+ + class + HasIter(typing.Protocol): +
+ +
+ View Source +
49@runtime_checkable
+50class HasIter(Protocol):
+51    """Workaround for mypy P.args."""
+52
+53    def __iter__(self) -> Iterator[Any]:
+54        """Object can be iterated.
+55
+56        Yields:
+57            Next object.
+58        """
+59        ...
+
+ +
+ +

Workaround for mypy P.args.

+
+ + +
+
#   + + + HasIter(*args, **kwargs) +
+ +
+ View Source +
1429def _no_init_or_replace_init(self, *args, **kwargs):
+1430    cls = type(self)
+1431
+1432    if cls._is_protocol:
+1433        raise TypeError('Protocols cannot be instantiated')
+1434
+1435    # Already using a custom `__init__`. No need to calculate correct
+1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
+1437    if cls.__init__ is not _no_init_or_replace_init:
+1438        return
+1439
+1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
+1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
+1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
+1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
+1444    # instantiation of the protocol subclass will thus use the new
+1445    # `__init__` and no longer call `_no_init_or_replace_init`.
+1446    for base in cls.__mro__:
+1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
+1448        if init is not _no_init_or_replace_init:
+1449            cls.__init__ = init
+1450            break
+1451    else:
+1452        # should not happen
+1453        cls.__init__ = object.__init__
+1454
+1455    cls.__init__(self, *args, **kwargs)
+
+ +
+ + + +
+
+
+
+ #   + +
@runtime_checkable
+ + class + HasItems(typing.Protocol): +
+ +
+ View Source +
64@runtime_checkable
+65class HasItems(Protocol):
+66    """Workaround for mypy P.kwargs."""
+67
+68    def items(self) -> Tuple[str, Any]:
+69        """Object has items method.
+70
+71        Returns:
+72            The dict of items.
+73        """
+74        ...
+
+ +
+ +

Workaround for mypy P.kwargs.

+
+ + +
+
#   + + + HasItems(*args, **kwargs) +
+ +
+ View Source +
1429def _no_init_or_replace_init(self, *args, **kwargs):
+1430    cls = type(self)
+1431
+1432    if cls._is_protocol:
+1433        raise TypeError('Protocols cannot be instantiated')
+1434
+1435    # Already using a custom `__init__`. No need to calculate correct
+1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
+1437    if cls.__init__ is not _no_init_or_replace_init:
+1438        return
+1439
+1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
+1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
+1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
+1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
+1444    # instantiation of the protocol subclass will thus use the new
+1445    # `__init__` and no longer call `_no_init_or_replace_init`.
+1446    for base in cls.__mro__:
+1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
+1448        if init is not _no_init_or_replace_init:
+1449            cls.__init__ = init
+1450            break
+1451    else:
+1452        # should not happen
+1453        cls.__init__ = object.__init__
+1454
+1455    cls.__init__(self, *args, **kwargs)
+
+ +
+ + + +
+
+
#   + + + def + items(self) -> tuple[str, typing.Any]: +
+ +
+ View Source +
68    def items(self) -> Tuple[str, Any]:
+69        """Object has items method.
+70
+71        Returns:
+72            The dict of items.
+73        """
+74        ...
+
+ +
+ +

Object has items method.

+ +

Returns: + The dict of items.

+
+ + +
+
+
+
+ #   + + + class + EscapedText(builtins.str): +
+ +
+ View Source +
77class EscapedText(str):
+78    """Text that has been escaped for regex.
+79
+80    Arguments:
+81        str -- Extend the string class.
+82    """
+83
+84    def __new__(cls, value: str) -> EscapedText:
+85        """Return a escaped regex string.
+86
+87        Arguments:
+88            value -- the string to escape
+89
+90        Returns:
+91            _description_
+92        """
+93        return str.__new__(cls, re.escape(value))
+
+ +
+ +

Text that has been escaped for regex.

+ +

Arguments: + str -- Extend the string class.

+
+ + +
+
#   + + + EscapedText(value: str) +
+ +
+ View Source +
84    def __new__(cls, value: str) -> EscapedText:
+85        """Return a escaped regex string.
+86
+87        Arguments:
+88            value -- the string to escape
+89
+90        Returns:
+91            _description_
+92        """
+93        return str.__new__(cls, re.escape(value))
+
+ +
+ +

Return a escaped regex string.

+ +

Arguments: + value -- the string to escape

+ +

Returns: + _description_

+
+ + +
+
+
Inherited Members
+
+
builtins.str
+
encode
+
replace
+
split
+
rsplit
+
join
+
capitalize
+
casefold
+
title
+
center
+
count
+
expandtabs
+
find
+
partition
+
index
+
ljust
+
lower
+
lstrip
+
rfind
+
rindex
+
rjust
+
rstrip
+
rpartition
+
splitlines
+
strip
+
swapcase
+
translate
+
upper
+
startswith
+
endswith
+
removeprefix
+
removesuffix
+
isascii
+
islower
+
isupper
+
istitle
+
isspace
+
isdecimal
+
isdigit
+
isnumeric
+
isalpha
+
isalnum
+
isidentifier
+
isprintable
+
zfill
+
format
+
format_map
+
maketrans
+ +
+
+
+
+
+
#   + + + def + re_escape( + func: collections.abc.Callable[~P, ~R] +) -> collections.abc.Callable[~P, ~R]: +
+ +
+ View Source +
 96def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+ 97    """Automatically escape any string parameters as EscapedText.
+ 98
+ 99    Arguments:
+100        func -- The function to decorate.
+101
+102    Returns:
+103        The decorated function.
+104    """
+105
+106    @wraps(func)
+107    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+108        escaped_args: List[Any] = []
+109        escaped_kwargs: Dict[str, Any] = {}
+110        for arg in cast(HasIter, args):
+111            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+112                escaped_args.append(EscapedText(arg))
+113            else:
+114                escaped_args.append(arg)
+115        arg_k: str
+116        arg_v: Any
+117        for arg_k, arg_v in cast(HasItems, kwargs).items():
+118            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+119                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+120            else:
+121                escaped_kwargs[arg_k] = arg_v
+122        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+123
+124    return inner
+
+ +
+ +

Automatically escape any string parameters as EscapedText.

+ +

Arguments: + func -- The function to decorate.

+ +

Returns: + The decorated function.

+
+ + +
+
+
+ #   + + + class + CharClass(enum.Enum): +
+ +
+ View Source +
127class CharClass(Enum):
+128    """Enum of character classes in regex.
+129
+130    Arguments:
+131        Enum -- Extends the Enum class.
+132    """
+133
+134    DIGIT = "\\d"
+135    LETTER = "\\w"
+136    UPPERCASE_LETTER = "\\u"
+137    LOWERCASE_LETTER = "\\l"
+138    WHITESPACE = "\\s"
+139    TAB = "\\t"
+140
+141    def __str__(self) -> str:
+142        """To string method based on Enum value.
+143
+144        Returns:
+145            value of Enum
+146        """
+147        return self.value
+
+ +
+ +

Enum of character classes in regex.

+ +

Arguments: + Enum -- Extends the Enum class.

+
+ + +
+
#   + + DIGIT = <CharClass.DIGIT: '\\d'> +
+ + + + +
+
+
#   + + LETTER = <CharClass.LETTER: '\\w'> +
+ + + + +
+
+
#   + + UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'> +
+ + + + +
+
+
#   + + LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'> +
+ + + + +
+
+
#   + + WHITESPACE = <CharClass.WHITESPACE: '\\s'> +
+ + + + +
+
+
#   + + TAB = <CharClass.TAB: '\\t'> +
+ + + + +
+
+
Inherited Members
+
+
enum.Enum
+
name
+
value
+ +
+
+
+
+
+
+ #   + + + class + SpecialChar(enum.Enum): +
+ +
+ View Source +
150class SpecialChar(Enum):
+151    """Enum of special charaters, shorthand.
+152
+153    Arguments:
+154        Enum -- Extends the Enum class.
+155    """
+156
+157    # does not work  / should not be used in [ ]
+158    LINEBREAK = "(\\n|(\\r\\n))"
+159    START_OF_LINE = "^"
+160    END_OF_LINE = "$"
+161    TAB = "\t"
+162
+163    def __str__(self) -> str:
+164        """To string for special chars enum.
+165
+166        Returns:
+167            Return value of enum as string.
+168        """
+169        return self.value
+
+ +
+ +

Enum of special charaters, shorthand.

+ +

Arguments: + Enum -- Extends the Enum class.

+
+ + +
+
#   + + LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'> +
+ + + + +
+
+
#   + + START_OF_LINE = <SpecialChar.START_OF_LINE: '^'> +
+ + + + +
+
+
#   + + END_OF_LINE = <SpecialChar.END_OF_LINE: '$'> +
+ + + + +
+
+
#   + + TAB = <SpecialChar.TAB: '\t'> +
+ + + + +
+
+
Inherited Members
+
+
enum.Enum
+
name
+
value
+ +
+
+
+
+
+
#   + + CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass] +
+ + + + +
+
+
#   + + EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +
+ + + + +
+
+
#   + + VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +
+ + + + +
+
+
+ #   + + + class + Verbex: +
+ +
+ View Source +
197class Verbex:
+198    """
+199    VerbalExpressions class.
+200
+201    the following methods do not try to match the original js lib!
+202    """
+203
+204    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+205
+206    @re_escape
+207    @beartype
+208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+209        """Create a Verbex object; setting any needed flags.
+210
+211        Keyword Arguments:
+212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+213        """
+214        # self._parts: List[str] = [text]
+215        self._parts: List[str] = []
+216        self._modifiers = modifiers
+217
+218    @property
+219    def modifiers(self) -> re.RegexFlag:
+220        """Return the modifiers for this Verbex object.
+221
+222        Returns:
+223            The modifiers applied to this object.
+224        """
+225        return self._modifiers
+226
+227    def __str__(self) -> str:
+228        """Return regex string representation."""
+229        return "".join(self._parts)
+230
+231    @beartype
+232    def _add(self, value: Union[str, List[str]]) -> Verbex:
+233        """
+234        Append a transformed value to internal expression to be compiled.
+235
+236        As possible, this method should be "private".
+237        """
+238        if isinstance(value, list):
+239            self._parts.extend(value)
+240        else:
+241            self._parts.append(value)
+242        return self
+243
+244    def regex(self) -> Pattern[str]:
+245        """Get a regular expression object."""
+246        return re.compile(
+247            str(self),
+248            self._modifiers,
+249        )
+250
+251    # allow VerbexEscapedCharClassOrSpecial
+252
+253    @re_escape
+254    @beartype
+255    def _capture_group_with_name(
+256        self,
+257        name: str,
+258        text: VerbexEscapedCharClassOrSpecial,
+259    ) -> Verbex:
+260        return self._add(f"(?<{name}>{str(text)})")
+261
+262    @re_escape
+263    @beartype
+264    def _capture_group_without_name(
+265        self,
+266        text: VerbexEscapedCharClassOrSpecial,
+267    ) -> Verbex:
+268        return self._add(f"({str(text)})")
+269
+270    @re_escape
+271    @beartype
+272    @_poseur_decorator("self")
+273    def capture_group(
+274        self,
+275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+277    ) -> Verbex:
+278        """Create a capture group.
+279
+280        Name is optional if not specified then the first argument is the text.
+281
+282        Keyword Arguments:
+283            name_or_text -- The name of the group / text to search for (default: {None})
+284            text -- The text to search for (default: {None})
+285
+286        Raises:
+287            ValueError: If name is specified then text must be as well.
+288
+289        Returns:
+290            Verbex with added capture group.
+291        """
+292        if name_or_text is not None:
+293            if text is None:
+294                _text = name_or_text
+295                return self._capture_group_without_name(_text)
+296            if isinstance(name_or_text, str):
+297                return self._capture_group_with_name(name_or_text, text)
+298        raise ValueError("text must be specified with optional name")
+299
+300    @re_escape
+301    @beartype
+302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+303        """`or` is a python keyword so we use `OR` instead.
+304
+305        Arguments:
+306            text -- Text to find or a Verbex object.
+307
+308        Returns:
+309            Modified Verbex object.
+310        """
+311        return self._add("|").find(text)
+312
+313    @re_escape
+314    @beartype
+315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+316        """Find the text or Verbex object zero or more times.
+317
+318        Arguments:
+319            text -- The text / Verbex object to look for.
+320
+321        Returns:
+322            Modified Verbex object.
+323        """
+324        return self._add(f"(?:{str(text)})*")
+325
+326    @re_escape
+327    @beartype
+328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+329        """Find the text or Verbex object one or more times.
+330
+331        Arguments:
+332            text -- The text / Verbex object to look for.
+333
+334        Returns:
+335            Modified Verbex object.
+336        """
+337        return self._add(f"(?:{str(text)})+")
+338
+339    @re_escape
+340    @beartype
+341    def n_times(
+342        self,
+343        text: VerbexEscapedCharClassOrSpecial,
+344        n: int,  # noqa: VNE001
+345    ) -> Verbex:
+346        """Find the text or Verbex object n or more times.
+347
+348        Arguments:
+349            text -- The text / Verbex object to look for.
+350
+351        Returns:
+352            Modified Verbex object.
+353        """
+354        return self._add(f"(?:{str(text)}){{{n}}}")
+355
+356    @re_escape
+357    @beartype
+358    def n_times_or_more(
+359        self,
+360        text: VerbexEscapedCharClassOrSpecial,
+361        n: int,  # noqa: VNE001
+362    ) -> Verbex:
+363        """Find the text or Verbex object at least n times.
+364
+365        Arguments:
+366            text -- The text / Verbex object to look for.
+367
+368        Returns:
+369            Modified Verbex object.
+370        """
+371        return self._add(f"(?:{str(text)}){{{n},}}")
+372
+373    @re_escape
+374    @beartype
+375    def n_to_m_times(
+376        self,
+377        text: VerbexEscapedCharClassOrSpecial,
+378        n: int,  # noqa: VNE001
+379        m: int,  # noqa: VNE001
+380    ) -> Verbex:
+381        """Find the text or Verbex object between n and m times.
+382
+383        Arguments:
+384            text -- The text / Verbex object to look for.
+385
+386        Returns:
+387            Modified Verbex object.
+388        """
+389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+390
+391    @re_escape
+392    @beartype
+393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+394        """Possibly find the text / Verbex object.
+395
+396        Arguments:
+397            text -- The text / Verbex object to possibly find.
+398
+399        Returns:
+400            Modified Verbex object.
+401        """
+402        return self._add(f"(?:{str(text)})?")
+403
+404    @re_escape
+405    @beartype
+406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+407        """Find the text or Verbex object.
+408
+409        Arguments:
+410            text -- The text / Verbex object to look for.
+411
+412        Returns:
+413            Modified Verbex object.
+414        """
+415        return self._add(str(text))
+416
+417    @re_escape
+418    @beartype
+419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+420        """Synonym for find.
+421
+422        Arguments:
+423            text -- The text / Verbex object to look for.
+424
+425        Returns:
+426            Modified Verbex object.
+427        """
+428        return self.find(text)
+429
+430    @re_escape
+431    @beartype
+432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+433        """Match if string is followed by text.
+434
+435        Positive lookahead
+436
+437        Returns:
+438            Modified Verbex object.
+439        """
+440        return self._add(f"(?={text})")
+441
+442    @re_escape
+443    @beartype
+444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+445        """Match if string is not followed by text.
+446
+447        Negative lookahead
+448
+449        Returns:
+450            Modified Verbex object.
+451        """
+452        return self._add(f"(?!{text})")
+453
+454    @re_escape
+455    @beartype
+456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+457        """Match if string is not preceded by text.
+458
+459        Positive lookbehind
+460
+461        Returns:
+462            Modified Verbex object.
+463        """
+464        return self._add(f"(?<={text})")
+465
+466    @re_escape
+467    @beartype
+468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+469        """Match if string is not preceded by text.
+470
+471        Negative Lookbehind
+472
+473        Returns:
+474            Modified Verbex object.
+475        """
+476        return self._add(f"(?<!{text})")
+477
+478    # only allow CharclassOrChars
+479
+480    @re_escape
+481    @beartype
+482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+483        """Find anything in this group of chars or char class.
+484
+485        Arguments:
+486            text -- The characters to look for.
+487
+488        Returns:
+489            Modified Verbex object.
+490        """
+491        return self._add(f"(?:[{chargroup}])")
+492
+493    @re_escape
+494    @beartype
+495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+496        """Find anything but this group of chars or char class.
+497
+498        Arguments:
+499            text -- The characters to not look for.
+500
+501        Returns:
+502            Modified Verbex object.
+503        """
+504        return self._add(f"(?:[^{text}])")
+505
+506    @re_escape
+507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+508        """Find anything one or more times but this group of chars or char class.
+509
+510        Arguments:
+511            text -- The characters to not look for.
+512
+513        Returns:
+514            Modified Verbex object.
+515        """
+516        return self._add(f"[^{chargroup}]+")
+517
+518    # no text input
+519
+520    def start_of_line(self) -> Verbex:
+521        """Find the start of the line.
+522
+523        Returns:
+524            Modified Verbex object.
+525        """
+526        return self.find(SpecialChar.START_OF_LINE)
+527
+528    def end_of_line(self) -> Verbex:
+529        """Find the end of the line.
+530
+531        Returns:
+532            Modified Verbex object.
+533        """
+534        return self.find(SpecialChar.END_OF_LINE)
+535
+536    def line_break(self) -> Verbex:
+537        """Find a line break.
+538
+539        Returns:
+540            Modified Verbex object.
+541        """
+542        return self.find(SpecialChar.LINEBREAK)
+543
+544    def tab(self) -> Verbex:
+545        """Find a tab.
+546
+547        Returns:
+548            Modified Verbex object.
+549        """
+550        return self.find(SpecialChar.TAB)
+551
+552    def anything(self) -> Verbex:
+553        """Find anything one or more time.
+554
+555        Returns:
+556            Modified Verbex object.
+557        """
+558        return self._add(".+")
+559
+560    def as_few(self) -> Verbex:
+561        """Modify previous search to not be greedy.
+562
+563        Returns:
+564            Modified Verbex object.
+565        """
+566        return self._add("?")
+567
+568    @beartype
+569    def number_range(self, start: int, end: int) -> Verbex:
+570        """Generate a range of numbers.
+571
+572        Arguments:
+573            start -- Start of the range
+574            end -- End of the range
+575
+576        Returns:
+577            Modified Verbex object.
+578        """
+579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+580
+581    @beartype
+582    def letter_range(self, start: Char, end: Char) -> Verbex:
+583        """Generate a range of letters.
+584
+585        Arguments:
+586            start -- Start of the range
+587            end -- End of the range
+588
+589        Returns:
+590            Modified Verbex object.
+591        """
+592        return self._add(f"[{start}-{end}]")
+593
+594    def word(self) -> Verbex:
+595        """Find a word on word boundary.
+596
+597        Returns:
+598            Modified Verbex object.
+599        """
+600        return self._add("(\\b\\w+\\b)")
+601
+602    # # --------------- modifiers ------------------------
+603
+604    def with_any_case(self) -> Verbex:
+605        """Modify Verbex object to be case insensitive.
+606
+607        Returns:
+608            Modified Verbex object.
+609        """
+610        self._modifiers |= re.IGNORECASE
+611        return self
+612
+613    def search_by_line(self) -> Verbex:
+614        """Search each line, ^ and $ match begining and end of line respectively.
+615
+616        Returns:
+617            Modified Verbex object.
+618        """
+619        self._modifiers |= re.MULTILINE
+620        return self
+621
+622    def with_ascii(self) -> Verbex:
+623        """Match ascii instead of unicode.
+624
+625        Returns:
+626            Modified Verbex object.
+627        """
+628        self._modifiers |= re.ASCII
+629        return self
+
+ +
+ +

VerbalExpressions class.

+ +

the following methods do not try to match the original js lib!

+
+ + +
+
#   + +
@re_escape
+
@beartype
+ + Verbex(modifiers: re.RegexFlag = ) +
+ +
+ View Source +
206    @re_escape
+207    @beartype
+208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+209        """Create a Verbex object; setting any needed flags.
+210
+211        Keyword Arguments:
+212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+213        """
+214        # self._parts: List[str] = [text]
+215        self._parts: List[str] = []
+216        self._modifiers = modifiers
+
+ +
+ +

Create a Verbex object; setting any needed flags.

+ +

Keyword Arguments: + modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

+
+ + +
+
+
#   + + EMPTY_REGEX_FLAG = +
+ + + + +
+
+
#   + + modifiers: re.RegexFlag +
+ + +

Return the modifiers for this Verbex object.

+ +

Returns: + The modifiers applied to this object.

+
+ + +
+
+
#   + + + def + regex(self) -> Pattern[str]: +
+ +
+ View Source +
244    def regex(self) -> Pattern[str]:
+245        """Get a regular expression object."""
+246        return re.compile(
+247            str(self),
+248            self._modifiers,
+249        )
+
+ +
+ +

Get a regular expression object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + capture_group( + self, + name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
270    @re_escape
+271    @beartype
+272    @_poseur_decorator("self")
+273    def capture_group(
+274        self,
+275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+277    ) -> Verbex:
+278        """Create a capture group.
+279
+280        Name is optional if not specified then the first argument is the text.
+281
+282        Keyword Arguments:
+283            name_or_text -- The name of the group / text to search for (default: {None})
+284            text -- The text to search for (default: {None})
+285
+286        Raises:
+287            ValueError: If name is specified then text must be as well.
+288
+289        Returns:
+290            Verbex with added capture group.
+291        """
+292        if name_or_text is not None:
+293            if text is None:
+294                _text = name_or_text
+295                return self._capture_group_without_name(_text)
+296            if isinstance(name_or_text, str):
+297                return self._capture_group_with_name(name_or_text, text)
+298        raise ValueError("text must be specified with optional name")
+
+ +
+ +

Create a capture group.

+ +

Name is optional if not specified then the first argument is the text.

+ +

Keyword Arguments: + name_or_text -- The name of the group / text to search for (default: {None}) + text -- The text to search for (default: {None})

+ +

Raises: + ValueError: If name is specified then text must be as well.

+ +

Returns: + Verbex with added capture group.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + OR( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
300    @re_escape
+301    @beartype
+302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
+303        """`or` is a python keyword so we use `OR` instead.
+304
+305        Arguments:
+306            text -- Text to find or a Verbex object.
+307
+308        Returns:
+309            Modified Verbex object.
+310        """
+311        return self._add("|").find(text)
+
+ +
+ +

or is a python keyword so we use OR instead.

+ +

Arguments: + text -- Text to find or a Verbex object.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + zero_or_more( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
313    @re_escape
+314    @beartype
+315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+316        """Find the text or Verbex object zero or more times.
+317
+318        Arguments:
+319            text -- The text / Verbex object to look for.
+320
+321        Returns:
+322            Modified Verbex object.
+323        """
+324        return self._add(f"(?:{str(text)})*")
+
+ +
+ +

Find the text or Verbex object zero or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + one_or_more( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
326    @re_escape
+327    @beartype
+328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+329        """Find the text or Verbex object one or more times.
+330
+331        Arguments:
+332            text -- The text / Verbex object to look for.
+333
+334        Returns:
+335            Modified Verbex object.
+336        """
+337        return self._add(f"(?:{str(text)})+")
+
+ +
+ +

Find the text or Verbex object one or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + n_times( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
339    @re_escape
+340    @beartype
+341    def n_times(
+342        self,
+343        text: VerbexEscapedCharClassOrSpecial,
+344        n: int,  # noqa: VNE001
+345    ) -> Verbex:
+346        """Find the text or Verbex object n or more times.
+347
+348        Arguments:
+349            text -- The text / Verbex object to look for.
+350
+351        Returns:
+352            Modified Verbex object.
+353        """
+354        return self._add(f"(?:{str(text)}){{{n}}}")
+
+ +
+ +

Find the text or Verbex object n or more times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + n_times_or_more( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
356    @re_escape
+357    @beartype
+358    def n_times_or_more(
+359        self,
+360        text: VerbexEscapedCharClassOrSpecial,
+361        n: int,  # noqa: VNE001
+362    ) -> Verbex:
+363        """Find the text or Verbex object at least n times.
+364
+365        Arguments:
+366            text -- The text / Verbex object to look for.
+367
+368        Returns:
+369            Modified Verbex object.
+370        """
+371        return self._add(f"(?:{str(text)}){{{n},}}")
+
+ +
+ +

Find the text or Verbex object at least n times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + n_to_m_times( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], + n: int, + m: int +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
373    @re_escape
+374    @beartype
+375    def n_to_m_times(
+376        self,
+377        text: VerbexEscapedCharClassOrSpecial,
+378        n: int,  # noqa: VNE001
+379        m: int,  # noqa: VNE001
+380    ) -> Verbex:
+381        """Find the text or Verbex object between n and m times.
+382
+383        Arguments:
+384            text -- The text / Verbex object to look for.
+385
+386        Returns:
+387            Modified Verbex object.
+388        """
+389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+
+ +
+ +

Find the text or Verbex object between n and m times.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + maybe( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
391    @re_escape
+392    @beartype
+393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+394        """Possibly find the text / Verbex object.
+395
+396        Arguments:
+397            text -- The text / Verbex object to possibly find.
+398
+399        Returns:
+400            Modified Verbex object.
+401        """
+402        return self._add(f"(?:{str(text)})?")
+
+ +
+ +

Possibly find the text / Verbex object.

+ +

Arguments: + text -- The text / Verbex object to possibly find.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + find( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
404    @re_escape
+405    @beartype
+406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+407        """Find the text or Verbex object.
+408
+409        Arguments:
+410            text -- The text / Verbex object to look for.
+411
+412        Returns:
+413            Modified Verbex object.
+414        """
+415        return self._add(str(text))
+
+ +
+ +

Find the text or Verbex object.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + then( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
417    @re_escape
+418    @beartype
+419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+420        """Synonym for find.
+421
+422        Arguments:
+423            text -- The text / Verbex object to look for.
+424
+425        Returns:
+426            Modified Verbex object.
+427        """
+428        return self.find(text)
+
+ +
+ +

Synonym for find.

+ +

Arguments: + text -- The text / Verbex object to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + followed_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
430    @re_escape
+431    @beartype
+432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+433        """Match if string is followed by text.
+434
+435        Positive lookahead
+436
+437        Returns:
+438            Modified Verbex object.
+439        """
+440        return self._add(f"(?={text})")
+
+ +
+ +

Match if string is followed by text.

+ +

Positive lookahead

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + not_followed_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
442    @re_escape
+443    @beartype
+444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+445        """Match if string is not followed by text.
+446
+447        Negative lookahead
+448
+449        Returns:
+450            Modified Verbex object.
+451        """
+452        return self._add(f"(?!{text})")
+
+ +
+ +

Match if string is not followed by text.

+ +

Negative lookahead

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + preceded_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
454    @re_escape
+455    @beartype
+456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+457        """Match if string is not preceded by text.
+458
+459        Positive lookbehind
+460
+461        Returns:
+462            Modified Verbex object.
+463        """
+464        return self._add(f"(?<={text})")
+
+ +
+ +

Match if string is not preceded by text.

+ +

Positive lookbehind

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + not_preceded_by( + self, + text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
466    @re_escape
+467    @beartype
+468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+469        """Match if string is not preceded by text.
+470
+471        Negative Lookbehind
+472
+473        Returns:
+474            Modified Verbex object.
+475        """
+476        return self._add(f"(?<!{text})")
+
+ +
+ +

Match if string is not preceded by text.

+ +

Negative Lookbehind

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + any_of( + self, + chargroup: Union[str, verbex.verbex.CharClass] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
480    @re_escape
+481    @beartype
+482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+483        """Find anything in this group of chars or char class.
+484
+485        Arguments:
+486            text -- The characters to look for.
+487
+488        Returns:
+489            Modified Verbex object.
+490        """
+491        return self._add(f"(?:[{chargroup}])")
+
+ +
+ +

Find anything in this group of chars or char class.

+ +

Arguments: + text -- The characters to look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+
@beartype
+ + def + not_any_of( + self, + text: Union[str, verbex.verbex.CharClass] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
493    @re_escape
+494    @beartype
+495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+496        """Find anything but this group of chars or char class.
+497
+498        Arguments:
+499            text -- The characters to not look for.
+500
+501        Returns:
+502            Modified Verbex object.
+503        """
+504        return self._add(f"(?:[^{text}])")
+
+ +
+ +

Find anything but this group of chars or char class.

+ +

Arguments: + text -- The characters to not look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@re_escape
+ + def + anything_but( + self, + chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
506    @re_escape
+507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+508        """Find anything one or more times but this group of chars or char class.
+509
+510        Arguments:
+511            text -- The characters to not look for.
+512
+513        Returns:
+514            Modified Verbex object.
+515        """
+516        return self._add(f"[^{chargroup}]+")
+
+ +
+ +

Find anything one or more times but this group of chars or char class.

+ +

Arguments: + text -- The characters to not look for.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + start_of_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
520    def start_of_line(self) -> Verbex:
+521        """Find the start of the line.
+522
+523        Returns:
+524            Modified Verbex object.
+525        """
+526        return self.find(SpecialChar.START_OF_LINE)
+
+ +
+ +

Find the start of the line.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + end_of_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
528    def end_of_line(self) -> Verbex:
+529        """Find the end of the line.
+530
+531        Returns:
+532            Modified Verbex object.
+533        """
+534        return self.find(SpecialChar.END_OF_LINE)
+
+ +
+ +

Find the end of the line.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + line_break(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
536    def line_break(self) -> Verbex:
+537        """Find a line break.
+538
+539        Returns:
+540            Modified Verbex object.
+541        """
+542        return self.find(SpecialChar.LINEBREAK)
+
+ +
+ +

Find a line break.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + tab(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
544    def tab(self) -> Verbex:
+545        """Find a tab.
+546
+547        Returns:
+548            Modified Verbex object.
+549        """
+550        return self.find(SpecialChar.TAB)
+
+ +
+ +

Find a tab.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + anything(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
552    def anything(self) -> Verbex:
+553        """Find anything one or more time.
+554
+555        Returns:
+556            Modified Verbex object.
+557        """
+558        return self._add(".+")
+
+ +
+ +

Find anything one or more time.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + as_few(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
560    def as_few(self) -> Verbex:
+561        """Modify previous search to not be greedy.
+562
+563        Returns:
+564            Modified Verbex object.
+565        """
+566        return self._add("?")
+
+ +
+ +

Modify previous search to not be greedy.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@beartype
+ + def + number_range(self, start: int, end: int) -> verbex.verbex.Verbex: +
+ +
+ View Source +
568    @beartype
+569    def number_range(self, start: int, end: int) -> Verbex:
+570        """Generate a range of numbers.
+571
+572        Arguments:
+573            start -- Start of the range
+574            end -- End of the range
+575
+576        Returns:
+577            Modified Verbex object.
+578        """
+579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+
+ +
+ +

Generate a range of numbers.

+ +

Arguments: + start -- Start of the range + end -- End of the range

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + +
@beartype
+ + def + letter_range( + self, + start: typing.Annotated[str, Is[_string_len_is_1]], + end: typing.Annotated[str, Is[_string_len_is_1]] +) -> verbex.verbex.Verbex: +
+ +
+ View Source +
581    @beartype
+582    def letter_range(self, start: Char, end: Char) -> Verbex:
+583        """Generate a range of letters.
+584
+585        Arguments:
+586            start -- Start of the range
+587            end -- End of the range
+588
+589        Returns:
+590            Modified Verbex object.
+591        """
+592        return self._add(f"[{start}-{end}]")
+
+ +
+ +

Generate a range of letters.

+ +

Arguments: + start -- Start of the range + end -- End of the range

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + word(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
594    def word(self) -> Verbex:
+595        """Find a word on word boundary.
+596
+597        Returns:
+598            Modified Verbex object.
+599        """
+600        return self._add("(\\b\\w+\\b)")
+
+ +
+ +

Find a word on word boundary.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + with_any_case(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
604    def with_any_case(self) -> Verbex:
+605        """Modify Verbex object to be case insensitive.
+606
+607        Returns:
+608            Modified Verbex object.
+609        """
+610        self._modifiers |= re.IGNORECASE
+611        return self
+
+ +
+ +

Modify Verbex object to be case insensitive.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + search_by_line(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
613    def search_by_line(self) -> Verbex:
+614        """Search each line, ^ and $ match begining and end of line respectively.
+615
+616        Returns:
+617            Modified Verbex object.
+618        """
+619        self._modifiers |= re.MULTILINE
+620        return self
+
+ +
+ +

Search each line, ^ and $ match begining and end of line respectively.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
#   + + + def + with_ascii(self) -> verbex.verbex.Verbex: +
+ +
+ View Source +
622    def with_ascii(self) -> Verbex:
+623        """Match ascii instead of unicode.
+624
+625        Returns:
+626            Modified Verbex object.
+627        """
+628        self._modifiers |= re.ASCII
+629        return self
+
+ +
+ +

Match ascii instead of unicode.

+ +

Returns: + Modified Verbex object.

+
+ + +
+
+
+ + \ No newline at end of file From f1d1caff6cb6e0eb7a2559c9fc167a909283b2bc Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:43:55 -0400 Subject: [PATCH 56/90] use importlib_metadata for python 3.7 --- docs/verbex.html | 17 ++++++++++------- verbex/__init__.py | 7 +++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/verbex.html b/docs/verbex.html index 189cab6..c89b400 100644 --- a/docs/verbex.html +++ b/docs/verbex.html @@ -44,13 +44,16 @@

View Source -
0import importlib.metadata
-1
-2from .verbex import CharClass as CharClass
-3from .verbex import SpecialChar as SpecialChar
-4from .verbex import Verbex as Verbex
-5
-6__version__ = importlib.metadata.version("verbex")
+            
0try:
+1    from importlib.metadata import version
+2except ImportError:
+3    from importlib_metadata import version  # type: ignore
+4
+5from .verbex import CharClass as CharClass
+6from .verbex import SpecialChar as SpecialChar
+7from .verbex import Verbex as Verbex
+8
+9__version__ = version("verbex")
 
diff --git a/verbex/__init__.py b/verbex/__init__.py index 566fb9a..9d5e731 100644 --- a/verbex/__init__.py +++ b/verbex/__init__.py @@ -1,7 +1,10 @@ -import importlib.metadata +try: + from importlib.metadata import version +except ImportError: + from importlib_metadata import version # type: ignore from .verbex import CharClass as CharClass from .verbex import SpecialChar as SpecialChar from .verbex import Verbex as Verbex -__version__ = importlib.metadata.version("verbex") +__version__ = version("verbex") From a04e3cebddc9187338ac62c5256128b4b4095df5 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 17:52:35 -0400 Subject: [PATCH 57/90] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e390a94..b9c53b1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Verbex: Python verbal based regular expressions ![Build Status](https://github.com/rbroderi/Verbex/actions/workflows/main.yml/badge.svg?event=push) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) +[![PyPI license](https://pypi.org/project/Verbex/)](https://www.gnu.org/licenses/gpl-3.0.en.html) ## Installation ```bash From 8bcc875dea72171ec3af63848222ca07e6f4c3da Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 18:00:28 -0400 Subject: [PATCH 58/90] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b9c53b1..2cb43f2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ Verbex: Python verbal based regular expressions ![Build Status](https://github.com/rbroderi/Verbex/actions/workflows/main.yml/badge.svg?event=push) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) -[![PyPI license](https://pypi.org/project/Verbex/)](https://www.gnu.org/licenses/gpl-3.0.en.html) +[![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) +[![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) +[![Generic badge](https://img.shields.io/badge/mypy-passed-green.svg)](https://shields.io/) ## Installation ```bash From cc9a299178bdfe494f8b6856739a6d40e706dfc6 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 22:04:37 -0400 Subject: [PATCH 59/90] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2cb43f2..96f303c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Verbex: Python verbal based regular expressions [![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-passed-green.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/bandit-passed-green.svg)](https://shields.io/) ## Installation ```bash From 161b9211d07e3102e239bb0665bd6eeee82fee6a Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 22:07:26 -0400 Subject: [PATCH 60/90] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 96f303c..3a0a2b4 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ Verbex: Python verbal based regular expressions [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) -[![Generic badge](https://img.shields.io/badge/mypy-passed-green.svg)](https://shields.io/) -[![Generic badge](https://img.shields.io/badge/bandit-passed-green.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/flake8-linted-yellow.svg)](https://shields.io/) ## Installation ```bash From 10145fdf296c959f8b50f0855316d363f2d0e2a5 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 8 May 2022 22:11:03 -0400 Subject: [PATCH 61/90] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3a0a2b4..1e7daa8 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ Verbex: Python verbal based regular expressions [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) -[![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](https://shields.io/) -[![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://shields.io/) -[![Generic badge](https://img.shields.io/badge/flake8-linted-yellow.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) +[![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) +[![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) +[![Generic badge](https://img.shields.io/badge/flake8-linted-yellow.svg)](https://github.com/pycqa/flake8) ## Installation ```bash From fa07e9c2a8d1895032a72bd331e9e19039dc2b3b Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 11 May 2022 21:20:07 -0400 Subject: [PATCH 62/90] add pycln --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f5d3702..516a623 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,6 +52,11 @@ repos: name: "Python: Formating files" args: [--line-length=88, --preview, --safe] types: [file, python] + - repo: https://github.com/hadialqattan/pycln + rev: v1.3.2 + hooks: + - id: pycln + name: "Python: remove unused imports." - repo: https://github.com/asottile/blacken-docs rev: v1.12.1 hooks: From e55e48e21e8f227012fca975e5aba37afaaa5d38 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 11 May 2022 22:29:09 -0400 Subject: [PATCH 63/90] updated pre-commit tests --- .pre-commit-config.yaml | 15 +- .vscode/settings.json | 4 + check_names.py | 26 +- docs/verbex/verbex.html | 2881 ++++++++++++++++++++------------------- verbex/verbex.py | 11 +- 5 files changed, 1479 insertions(+), 1458 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 516a623..b6a5152 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,6 +52,12 @@ repos: name: "Python: Formating files" args: [--line-length=88, --preview, --safe] types: [file, python] + - repo: https://github.com/asottile/pyupgrade + rev: v2.32.1 + hooks: + - id: pyupgrade + name: "Python: upgrade syntax" + args: [--py37-plus] - repo: https://github.com/hadialqattan/pycln rev: v1.3.2 hooks: @@ -93,6 +99,7 @@ repos: # making isort line length compatible with black - "--max-line-length=88" - "--max-complexity=18" + - "--kwargs-max-positional-arguments=4" # allowing these errors now that in the past we ignored. # D100 Missing docstring in public module # D103 Missing docstring in public function @@ -131,7 +138,13 @@ repos: - flake8-colors - flake8-tuple - pandas-vet - # - wemake-python-styleguide + - flake8-length + - flake8-assertive + - flake8-warnings + - flake8-comprehensions + - flake8-simplify + - flake8-noqa + - flake8-force-keyword-arguments exclude: "setup[.]py|conf[.]py|__init__[.]py" types: [file, python] - repo: https://github.com/asottile/add-trailing-comma diff --git a/.vscode/settings.json b/.vscode/settings.json index 19a66dd..3a46794 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,9 @@ "pylance", "pyright", "Verbex" + ], + "python.linting.flake8Enabled": true, + "python.linting.flake8Args": [ + "--ignore=E501" ] } diff --git a/check_names.py b/check_names.py index b6d6eeb..3525104 100644 --- a/check_names.py +++ b/check_names.py @@ -50,20 +50,18 @@ def main() -> int: print(f"ERROR: '{module_name}' is not all lowercase with underscores") return ExitCode.DATA_ERR # check package if exists - if package_name.strip() != "": - # check package name - if not re.fullmatch("[A-Za-z]+", package_name): - if re.fullmatch("[A-Za-z0-9]+", package_name): - print( - f"WARNING: '{package_name}' has numbers - allowing but note" - " this is not 'strictly' to pep 8 best practices", - ) - else: - print( - f"ERROR: '{package_name}' is not all lowercase with no" - " underscores", - ) - return ExitCode.DATA_ERR + # check package name + if package_name.strip() != "" and not re.fullmatch("[A-Za-z]+", package_name): + if re.fullmatch("[A-Za-z0-9]+", package_name): + print( + f"WARNING: '{package_name}' has numbers - allowing but note" + " this is not 'strictly' to pep 8 best practices", + ) + else: + print( + f"ERROR: '{package_name}' is not all lowercase with no underscores", + ) + return ExitCode.DATA_ERR return ExitCode.OK diff --git a/docs/verbex/verbex.html b/docs/verbex/verbex.html index 59dc123..17bbb44 100644 --- a/docs/verbex/verbex.html +++ b/docs/verbex/verbex.html @@ -261,631 +261,634 @@

15except ImportError: 16 from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable # type: ignore # <--- if Python < 3.9.0 # noqa E501 17 - 18from typing import Pattern, TypeVar + 18from typing import TYPE_CHECKING, Pattern, TypeVar 19 - 20from beartype import beartype # type: ignore - 21from beartype.typing import ( # type: ignore - 22 Any, - 23 Callable, - 24 Dict, - 25 Iterator, - 26 List, - 27 Optional, - 28 Tuple, - 29 Union, - 30 cast, - 31) - 32from beartype.vale import Is # type: ignore - 33 - 34 - 35def _string_len_is_1(text: object) -> bool: - 36 return isinstance(text, str) and len(text) == 1 + 20if TYPE_CHECKING: + 21 from typing import Protocol # noqa: F811 + 22 + 23from beartype import beartype # type: ignore + 24from beartype.typing import ( # type: ignore + 25 Any, + 26 Callable, + 27 Dict, + 28 Iterator, + 29 List, + 30 Optional, + 31 Tuple, + 32 Union, + 33 cast, + 34) + 35from beartype.vale import Is # type: ignore + 36 37 - 38 - 39Char = Annotated[str, Is[_string_len_is_1]] + 38def _string_len_is_1(text: object) -> bool: + 39 return isinstance(text, str) and len(text) == 1 40 41 - 42P = ParamSpec("P") # noqa VNE001 - 43R = TypeVar("R") # noqa VNE001 + 42Char = Annotated[str, Is[_string_len_is_1]] + 43 44 - 45 - 46# work around for bug https://github.com/python/mypy/issues/12660 - 47# fixed in next version of mypy. - 48@runtime_checkable - 49class HasIter(Protocol): - 50 """Workaround for mypy P.args.""" - 51 - 52 def __iter__(self) -> Iterator[Any]: - 53 """Object can be iterated. + 45P = ParamSpec("P") # noqa: VNE001 + 46R = TypeVar("R") # noqa: VNE001 + 47 + 48 + 49# work around for bug https://github.com/python/mypy/issues/12660 + 50# fixed in next version of mypy. + 51@runtime_checkable + 52class HasIter(Protocol): + 53 """Workaround for mypy P.args.""" 54 - 55 Yields: - 56 Next object. - 57 """ - 58 ... - 59 - 60 - 61# work around for bug https://github.com/python/mypy/issues/12660 - 62# fixed in next version of mypy - 63@runtime_checkable - 64class HasItems(Protocol): - 65 """Workaround for mypy P.kwargs.""" - 66 - 67 def items(self) -> Tuple[str, Any]: - 68 """Object has items method. + 55 def __iter__(self) -> Iterator[Any]: + 56 """Object can be iterated. + 57 + 58 Yields: + 59 Next object. + 60 """ + 61 ... + 62 + 63 + 64# work around for bug https://github.com/python/mypy/issues/12660 + 65# fixed in next version of mypy + 66@runtime_checkable + 67class HasItems(Protocol): + 68 """Workaround for mypy P.kwargs.""" 69 - 70 Returns: - 71 The dict of items. - 72 """ - 73 ... - 74 - 75 - 76class EscapedText(str): - 77 """Text that has been escaped for regex. + 70 def items(self) -> Tuple[str, Any]: + 71 """Object has items method. + 72 + 73 Returns: + 74 The dict of items. + 75 """ + 76 ... + 77 78 - 79 Arguments: - 80 str -- Extend the string class. - 81 """ - 82 - 83 def __new__(cls, value: str) -> EscapedText: - 84 """Return a escaped regex string. + 79class EscapedText(str): + 80 """Text that has been escaped for regex. + 81 + 82 Arguments: + 83 str -- Extend the string class. + 84 """ 85 - 86 Arguments: - 87 value -- the string to escape + 86 def __new__(cls, value: str) -> EscapedText: + 87 """Return a escaped regex string. 88 - 89 Returns: - 90 _description_ - 91 """ - 92 return str.__new__(cls, re.escape(value)) - 93 - 94 - 95def re_escape(func: Callable[P, R]) -> Callable[P, R]: - 96 """Automatically escape any string parameters as EscapedText. + 89 Arguments: + 90 value -- the string to escape + 91 + 92 Returns: + 93 _description_ + 94 """ + 95 return str.__new__(cls, re.escape(value)) + 96 97 - 98 Arguments: - 99 func -- The function to decorate. + 98def re_escape(func: Callable[P, R]) -> Callable[P, R]: + 99 """Automatically escape any string parameters as EscapedText. 100 -101 Returns: -102 The decorated function. -103 """ -104 -105 @wraps(func) -106 def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore -107 escaped_args: List[Any] = [] -108 escaped_kwargs: Dict[str, Any] = {} -109 for arg in cast(HasIter, args): -110 if not isinstance(arg, EscapedText) and isinstance(arg, str): -111 escaped_args.append(EscapedText(arg)) -112 else: -113 escaped_args.append(arg) -114 arg_k: str -115 arg_v: Any -116 for arg_k, arg_v in cast(HasItems, kwargs).items(): -117 if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): -118 escaped_kwargs[arg_k] = EscapedText(str(arg_v)) -119 else: -120 escaped_kwargs[arg_k] = arg_v -121 return func(*escaped_args, **escaped_kwargs) # type: ignore -122 -123 return inner -124 +101 Arguments: +102 func -- The function to decorate. +103 +104 Returns: +105 The decorated function. +106 """ +107 +108 @wraps(func) +109 def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore +110 escaped_args: List[Any] = [] +111 escaped_kwargs: Dict[str, Any] = {} +112 for arg in cast(HasIter, args): +113 if not isinstance(arg, EscapedText) and isinstance(arg, str): +114 escaped_args.append(EscapedText(arg)) +115 else: +116 escaped_args.append(arg) +117 arg_k: str +118 arg_v: Any +119 for arg_k, arg_v in cast(HasItems, kwargs).items(): +120 if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): +121 escaped_kwargs[arg_k] = EscapedText(str(arg_v)) +122 else: +123 escaped_kwargs[arg_k] = arg_v +124 return func(*escaped_args, **escaped_kwargs) # type: ignore 125 -126class CharClass(Enum): -127 """Enum of character classes in regex. +126 return inner +127 128 -129 Arguments: -130 Enum -- Extends the Enum class. -131 """ -132 -133 DIGIT = "\\d" -134 LETTER = "\\w" -135 UPPERCASE_LETTER = "\\u" -136 LOWERCASE_LETTER = "\\l" -137 WHITESPACE = "\\s" -138 TAB = "\\t" -139 -140 def __str__(self) -> str: -141 """To string method based on Enum value. +129class CharClass(Enum): +130 """Enum of character classes in regex. +131 +132 Arguments: +133 Enum -- Extends the Enum class. +134 """ +135 +136 DIGIT = "\\d" +137 LETTER = "\\w" +138 UPPERCASE_LETTER = "\\u" +139 LOWERCASE_LETTER = "\\l" +140 WHITESPACE = "\\s" +141 TAB = "\\t" 142 -143 Returns: -144 value of Enum -145 """ -146 return self.value -147 -148 -149class SpecialChar(Enum): -150 """Enum of special charaters, shorthand. +143 def __str__(self) -> str: +144 """To string method based on Enum value. +145 +146 Returns: +147 value of Enum +148 """ +149 return self.value +150 151 -152 Arguments: -153 Enum -- Extends the Enum class. -154 """ -155 -156 # does not work / should not be used in [ ] -157 LINEBREAK = "(\\n|(\\r\\n))" -158 START_OF_LINE = "^" -159 END_OF_LINE = "$" -160 TAB = "\t" -161 -162 def __str__(self) -> str: -163 """To string for special chars enum. +152class SpecialChar(Enum): +153 """Enum of special charaters, shorthand. +154 +155 Arguments: +156 Enum -- Extends the Enum class. +157 """ +158 +159 # does not work / should not be used in [ ] +160 LINEBREAK = "(\\n|(\\r\\n))" +161 START_OF_LINE = "^" +162 END_OF_LINE = "$" +163 TAB = "\t" 164 -165 Returns: -166 Return value of enum as string. -167 """ -168 return self.value -169 -170 -171CharClassOrChars: TypeAlias = Union[str, CharClass] -172EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] -173VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] -174 -175 -176def _poseur_decorator(*poseur: Any) -> Any: -177 """Positional-only arguments runtime checker.""" -178 import functools -179 -180 def caller(func: Callable[P, R]) -> Callable[P, R]: # type: ignore -181 @functools.wraps(func) -182 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: -183 poseur_args = set(poseur).intersection(kwargs) # type: ignore -184 if poseur_args: -185 raise TypeError( -186 "%s() got some positional-only arguments passed as keyword" -187 " arguments: %r" % (func.__name__, ", ".join(poseur_args)), -188 ) -189 return func(*args, **kwargs) # type: ignore -190 -191 return wrapper -192 -193 return caller -194 +165 def __str__(self) -> str: +166 """To string for special chars enum. +167 +168 Returns: +169 Return value of enum as string. +170 """ +171 return self.value +172 +173 +174CharClassOrChars: TypeAlias = Union[str, CharClass] +175EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] +176VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] +177 +178 +179def _poseur_decorator(*poseur: Any) -> Any: +180 """Positional-only arguments runtime checker.""" +181 import functools +182 +183 def caller(func: Callable[P, R]) -> Callable[P, R]: # type: ignore +184 @functools.wraps(func) +185 def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: +186 poseur_args = set(poseur).intersection(kwargs) # type: ignore +187 if poseur_args: +188 raise TypeError( +189 "%s() got some positional-only arguments passed as keyword" +190 " arguments: %r" % (func.__name__, ", ".join(poseur_args)), +191 ) +192 return func(*args, **kwargs) # type: ignore +193 +194 return wrapper 195 -196class Verbex: -197 """ -198 VerbalExpressions class. -199 -200 the following methods do not try to match the original js lib! -201 """ +196 return caller +197 +198 +199class Verbex: +200 """ +201 VerbalExpressions class. 202 -203 EMPTY_REGEX_FLAG = re.RegexFlag(0) -204 -205 @re_escape -206 @beartype -207 def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): -208 """Create a Verbex object; setting any needed flags. -209 -210 Keyword Arguments: -211 modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) -212 """ -213 # self._parts: List[str] = [text] -214 self._parts: List[str] = [] -215 self._modifiers = modifiers -216 -217 @property -218 def modifiers(self) -> re.RegexFlag: -219 """Return the modifiers for this Verbex object. -220 -221 Returns: -222 The modifiers applied to this object. -223 """ -224 return self._modifiers -225 -226 def __str__(self) -> str: -227 """Return regex string representation.""" -228 return "".join(self._parts) -229 -230 @beartype -231 def _add(self, value: Union[str, List[str]]) -> Verbex: -232 """ -233 Append a transformed value to internal expression to be compiled. -234 -235 As possible, this method should be "private". -236 """ -237 if isinstance(value, list): -238 self._parts.extend(value) -239 else: -240 self._parts.append(value) -241 return self -242 -243 def regex(self) -> Pattern[str]: -244 """Get a regular expression object.""" -245 return re.compile( -246 str(self), -247 self._modifiers, -248 ) -249 -250 # allow VerbexEscapedCharClassOrSpecial -251 -252 @re_escape -253 @beartype -254 def _capture_group_with_name( -255 self, -256 name: str, -257 text: VerbexEscapedCharClassOrSpecial, -258 ) -> Verbex: -259 return self._add(f"(?<{name}>{str(text)})") -260 -261 @re_escape -262 @beartype -263 def _capture_group_without_name( -264 self, -265 text: VerbexEscapedCharClassOrSpecial, -266 ) -> Verbex: -267 return self._add(f"({str(text)})") -268 -269 @re_escape -270 @beartype -271 @_poseur_decorator("self") -272 def capture_group( -273 self, -274 name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, -275 text: Optional[VerbexEscapedCharClassOrSpecial] = None, -276 ) -> Verbex: -277 """Create a capture group. -278 -279 Name is optional if not specified then the first argument is the text. -280 -281 Keyword Arguments: -282 name_or_text -- The name of the group / text to search for (default: {None}) -283 text -- The text to search for (default: {None}) -284 -285 Raises: -286 ValueError: If name is specified then text must be as well. +203 the following methods do not try to match the original js lib! +204 """ +205 +206 EMPTY_REGEX_FLAG = re.RegexFlag(0) +207 +208 @re_escape +209 @beartype +210 def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): +211 """Create a Verbex object; setting any needed flags. +212 +213 Keyword Arguments: +214 modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) +215 """ +216 # self._parts: List[str] = [text] +217 self._parts: List[str] = [] +218 self._modifiers = modifiers +219 +220 @property +221 def modifiers(self) -> re.RegexFlag: +222 """Return the modifiers for this Verbex object. +223 +224 Returns: +225 The modifiers applied to this object. +226 """ +227 return self._modifiers +228 +229 def __str__(self) -> str: +230 """Return regex string representation.""" +231 return "".join(self._parts) +232 +233 @beartype +234 def _add(self, value: Union[str, List[str]]) -> Verbex: +235 """ +236 Append a transformed value to internal expression to be compiled. +237 +238 As possible, this method should be "private". +239 """ +240 if isinstance(value, list): +241 self._parts.extend(value) +242 else: +243 self._parts.append(value) +244 return self +245 +246 def regex(self) -> Pattern[str]: +247 """Get a regular expression object.""" +248 return re.compile( +249 str(self), +250 self._modifiers, +251 ) +252 +253 # allow VerbexEscapedCharClassOrSpecial +254 +255 @re_escape +256 @beartype +257 def _capture_group_with_name( +258 self, +259 name: str, +260 text: VerbexEscapedCharClassOrSpecial, +261 ) -> Verbex: +262 return self._add(f"(?<{name}>{str(text)})") +263 +264 @re_escape +265 @beartype +266 def _capture_group_without_name( +267 self, +268 text: VerbexEscapedCharClassOrSpecial, +269 ) -> Verbex: +270 return self._add(f"({str(text)})") +271 +272 @re_escape +273 @beartype +274 @_poseur_decorator("self") +275 def capture_group( +276 self, +277 name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, +278 text: Optional[VerbexEscapedCharClassOrSpecial] = None, +279 ) -> Verbex: +280 """Create a capture group. +281 +282 Name is optional if not specified then the first argument is the text. +283 +284 Keyword Arguments: +285 name_or_text -- The name of the group / text to search for (default: {None}) +286 text -- The text to search for (default: {None}) 287 -288 Returns: -289 Verbex with added capture group. -290 """ -291 if name_or_text is not None: -292 if text is None: -293 _text = name_or_text -294 return self._capture_group_without_name(_text) -295 if isinstance(name_or_text, str): -296 return self._capture_group_with_name(name_or_text, text) -297 raise ValueError("text must be specified with optional name") -298 -299 @re_escape -300 @beartype -301 def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 -302 """`or` is a python keyword so we use `OR` instead. -303 -304 Arguments: -305 text -- Text to find or a Verbex object. +288 Raises: +289 ValueError: If name is specified then text must be as well. +290 +291 Returns: +292 Verbex with added capture group. +293 """ +294 if name_or_text is not None: +295 if text is None: +296 _text = name_or_text +297 return self._capture_group_without_name(_text) +298 if isinstance(name_or_text, str): +299 return self._capture_group_with_name(name_or_text, text) +300 raise ValueError("text must be specified with optional name") +301 +302 @re_escape +303 @beartype +304 def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 +305 """`or` is a python keyword so we use `OR` instead. 306 -307 Returns: -308 Modified Verbex object. -309 """ -310 return self._add("|").find(text) -311 -312 @re_escape -313 @beartype -314 def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -315 """Find the text or Verbex object zero or more times. -316 -317 Arguments: -318 text -- The text / Verbex object to look for. +307 Arguments: +308 text -- Text to find or a Verbex object. +309 +310 Returns: +311 Modified Verbex object. +312 """ +313 return self._add("|").find(text) +314 +315 @re_escape +316 @beartype +317 def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +318 """Find the text or Verbex object zero or more times. 319 -320 Returns: -321 Modified Verbex object. -322 """ -323 return self._add(f"(?:{str(text)})*") -324 -325 @re_escape -326 @beartype -327 def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -328 """Find the text or Verbex object one or more times. -329 -330 Arguments: -331 text -- The text / Verbex object to look for. +320 Arguments: +321 text -- The text / Verbex object to look for. +322 +323 Returns: +324 Modified Verbex object. +325 """ +326 return self._add(f"(?:{str(text)})*") +327 +328 @re_escape +329 @beartype +330 def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +331 """Find the text or Verbex object one or more times. 332 -333 Returns: -334 Modified Verbex object. -335 """ -336 return self._add(f"(?:{str(text)})+") -337 -338 @re_escape -339 @beartype -340 def n_times( -341 self, -342 text: VerbexEscapedCharClassOrSpecial, -343 n: int, # noqa: VNE001 -344 ) -> Verbex: -345 """Find the text or Verbex object n or more times. -346 -347 Arguments: -348 text -- The text / Verbex object to look for. +333 Arguments: +334 text -- The text / Verbex object to look for. +335 +336 Returns: +337 Modified Verbex object. +338 """ +339 return self._add(f"(?:{str(text)})+") +340 +341 @re_escape +342 @beartype +343 def n_times( +344 self, +345 text: VerbexEscapedCharClassOrSpecial, +346 n: int, # noqa: VNE001 +347 ) -> Verbex: +348 """Find the text or Verbex object n or more times. 349 -350 Returns: -351 Modified Verbex object. -352 """ -353 return self._add(f"(?:{str(text)}){{{n}}}") -354 -355 @re_escape -356 @beartype -357 def n_times_or_more( -358 self, -359 text: VerbexEscapedCharClassOrSpecial, -360 n: int, # noqa: VNE001 -361 ) -> Verbex: -362 """Find the text or Verbex object at least n times. -363 -364 Arguments: -365 text -- The text / Verbex object to look for. +350 Arguments: +351 text -- The text / Verbex object to look for. +352 +353 Returns: +354 Modified Verbex object. +355 """ +356 return self._add(f"(?:{str(text)}){{{n}}}") +357 +358 @re_escape +359 @beartype +360 def n_times_or_more( +361 self, +362 text: VerbexEscapedCharClassOrSpecial, +363 n: int, # noqa: VNE001 +364 ) -> Verbex: +365 """Find the text or Verbex object at least n times. 366 -367 Returns: -368 Modified Verbex object. -369 """ -370 return self._add(f"(?:{str(text)}){{{n},}}") -371 -372 @re_escape -373 @beartype -374 def n_to_m_times( -375 self, -376 text: VerbexEscapedCharClassOrSpecial, -377 n: int, # noqa: VNE001 -378 m: int, # noqa: VNE001 -379 ) -> Verbex: -380 """Find the text or Verbex object between n and m times. -381 -382 Arguments: -383 text -- The text / Verbex object to look for. +367 Arguments: +368 text -- The text / Verbex object to look for. +369 +370 Returns: +371 Modified Verbex object. +372 """ +373 return self._add(f"(?:{str(text)}){{{n},}}") +374 +375 @re_escape +376 @beartype +377 def n_to_m_times( +378 self, +379 text: VerbexEscapedCharClassOrSpecial, +380 n: int, # noqa: VNE001 +381 m: int, # noqa: VNE001 +382 ) -> Verbex: +383 """Find the text or Verbex object between n and m times. 384 -385 Returns: -386 Modified Verbex object. -387 """ -388 return self._add(f"(?:{str(text)}){{{n},{m}}}") -389 -390 @re_escape -391 @beartype -392 def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -393 """Possibly find the text / Verbex object. -394 -395 Arguments: -396 text -- The text / Verbex object to possibly find. +385 Arguments: +386 text -- The text / Verbex object to look for. +387 +388 Returns: +389 Modified Verbex object. +390 """ +391 return self._add(f"(?:{str(text)}){{{n},{m}}}") +392 +393 @re_escape +394 @beartype +395 def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +396 """Possibly find the text / Verbex object. 397 -398 Returns: -399 Modified Verbex object. -400 """ -401 return self._add(f"(?:{str(text)})?") -402 -403 @re_escape -404 @beartype -405 def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -406 """Find the text or Verbex object. -407 -408 Arguments: -409 text -- The text / Verbex object to look for. +398 Arguments: +399 text -- The text / Verbex object to possibly find. +400 +401 Returns: +402 Modified Verbex object. +403 """ +404 return self._add(f"(?:{str(text)})?") +405 +406 @re_escape +407 @beartype +408 def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +409 """Find the text or Verbex object. 410 -411 Returns: -412 Modified Verbex object. -413 """ -414 return self._add(str(text)) -415 -416 @re_escape -417 @beartype -418 def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -419 """Synonym for find. -420 -421 Arguments: -422 text -- The text / Verbex object to look for. +411 Arguments: +412 text -- The text / Verbex object to look for. +413 +414 Returns: +415 Modified Verbex object. +416 """ +417 return self._add(str(text)) +418 +419 @re_escape +420 @beartype +421 def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +422 """Synonym for find. 423 -424 Returns: -425 Modified Verbex object. -426 """ -427 return self.find(text) -428 -429 @re_escape -430 @beartype -431 def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -432 """Match if string is followed by text. -433 -434 Positive lookahead -435 -436 Returns: -437 Modified Verbex object. -438 """ -439 return self._add(f"(?={text})") -440 -441 @re_escape -442 @beartype -443 def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -444 """Match if string is not followed by text. -445 -446 Negative lookahead -447 -448 Returns: -449 Modified Verbex object. -450 """ -451 return self._add(f"(?!{text})") -452 -453 @re_escape -454 @beartype -455 def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -456 """Match if string is not preceded by text. -457 -458 Positive lookbehind -459 -460 Returns: -461 Modified Verbex object. -462 """ -463 return self._add(f"(?<={text})") -464 -465 @re_escape -466 @beartype -467 def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: -468 """Match if string is not preceded by text. -469 -470 Negative Lookbehind -471 -472 Returns: -473 Modified Verbex object. -474 """ -475 return self._add(f"(?<!{text})") -476 -477 # only allow CharclassOrChars -478 -479 @re_escape -480 @beartype -481 def any_of(self, chargroup: CharClassOrChars) -> Verbex: -482 """Find anything in this group of chars or char class. -483 -484 Arguments: -485 text -- The characters to look for. +424 Arguments: +425 text -- The text / Verbex object to look for. +426 +427 Returns: +428 Modified Verbex object. +429 """ +430 return self.find(text) +431 +432 @re_escape +433 @beartype +434 def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +435 """Match if string is followed by text. +436 +437 Positive lookahead +438 +439 Returns: +440 Modified Verbex object. +441 """ +442 return self._add(f"(?={text})") +443 +444 @re_escape +445 @beartype +446 def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +447 """Match if string is not followed by text. +448 +449 Negative lookahead +450 +451 Returns: +452 Modified Verbex object. +453 """ +454 return self._add(f"(?!{text})") +455 +456 @re_escape +457 @beartype +458 def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +459 """Match if string is not preceded by text. +460 +461 Positive lookbehind +462 +463 Returns: +464 Modified Verbex object. +465 """ +466 return self._add(f"(?<={text})") +467 +468 @re_escape +469 @beartype +470 def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: +471 """Match if string is not preceded by text. +472 +473 Negative Lookbehind +474 +475 Returns: +476 Modified Verbex object. +477 """ +478 return self._add(f"(?<!{text})") +479 +480 # only allow CharclassOrChars +481 +482 @re_escape +483 @beartype +484 def any_of(self, chargroup: CharClassOrChars) -> Verbex: +485 """Find anything in this group of chars or char class. 486 -487 Returns: -488 Modified Verbex object. -489 """ -490 return self._add(f"(?:[{chargroup}])") -491 -492 @re_escape -493 @beartype -494 def not_any_of(self, text: CharClassOrChars) -> Verbex: -495 """Find anything but this group of chars or char class. -496 -497 Arguments: -498 text -- The characters to not look for. +487 Arguments: +488 text -- The characters to look for. +489 +490 Returns: +491 Modified Verbex object. +492 """ +493 return self._add(f"(?:[{chargroup}])") +494 +495 @re_escape +496 @beartype +497 def not_any_of(self, text: CharClassOrChars) -> Verbex: +498 """Find anything but this group of chars or char class. 499 -500 Returns: -501 Modified Verbex object. -502 """ -503 return self._add(f"(?:[^{text}])") -504 -505 @re_escape -506 def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: -507 """Find anything one or more times but this group of chars or char class. -508 -509 Arguments: -510 text -- The characters to not look for. +500 Arguments: +501 text -- The characters to not look for. +502 +503 Returns: +504 Modified Verbex object. +505 """ +506 return self._add(f"(?:[^{text}])") +507 +508 @re_escape +509 def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: +510 """Find anything one or more times but this group of chars or char class. 511 -512 Returns: -513 Modified Verbex object. -514 """ -515 return self._add(f"[^{chargroup}]+") -516 -517 # no text input -518 -519 def start_of_line(self) -> Verbex: -520 """Find the start of the line. +512 Arguments: +513 text -- The characters to not look for. +514 +515 Returns: +516 Modified Verbex object. +517 """ +518 return self._add(f"[^{chargroup}]+") +519 +520 # no text input 521 -522 Returns: -523 Modified Verbex object. -524 """ -525 return self.find(SpecialChar.START_OF_LINE) -526 -527 def end_of_line(self) -> Verbex: -528 """Find the end of the line. +522 def start_of_line(self) -> Verbex: +523 """Find the start of the line. +524 +525 Returns: +526 Modified Verbex object. +527 """ +528 return self.find(SpecialChar.START_OF_LINE) 529 -530 Returns: -531 Modified Verbex object. -532 """ -533 return self.find(SpecialChar.END_OF_LINE) -534 -535 def line_break(self) -> Verbex: -536 """Find a line break. +530 def end_of_line(self) -> Verbex: +531 """Find the end of the line. +532 +533 Returns: +534 Modified Verbex object. +535 """ +536 return self.find(SpecialChar.END_OF_LINE) 537 -538 Returns: -539 Modified Verbex object. -540 """ -541 return self.find(SpecialChar.LINEBREAK) -542 -543 def tab(self) -> Verbex: -544 """Find a tab. +538 def line_break(self) -> Verbex: +539 """Find a line break. +540 +541 Returns: +542 Modified Verbex object. +543 """ +544 return self.find(SpecialChar.LINEBREAK) 545 -546 Returns: -547 Modified Verbex object. -548 """ -549 return self.find(SpecialChar.TAB) -550 -551 def anything(self) -> Verbex: -552 """Find anything one or more time. +546 def tab(self) -> Verbex: +547 """Find a tab. +548 +549 Returns: +550 Modified Verbex object. +551 """ +552 return self.find(SpecialChar.TAB) 553 -554 Returns: -555 Modified Verbex object. -556 """ -557 return self._add(".+") -558 -559 def as_few(self) -> Verbex: -560 """Modify previous search to not be greedy. +554 def anything(self) -> Verbex: +555 """Find anything one or more time. +556 +557 Returns: +558 Modified Verbex object. +559 """ +560 return self._add(".+") 561 -562 Returns: -563 Modified Verbex object. -564 """ -565 return self._add("?") -566 -567 @beartype -568 def number_range(self, start: int, end: int) -> Verbex: -569 """Generate a range of numbers. -570 -571 Arguments: -572 start -- Start of the range -573 end -- End of the range -574 -575 Returns: -576 Modified Verbex object. -577 """ -578 return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") -579 -580 @beartype -581 def letter_range(self, start: Char, end: Char) -> Verbex: -582 """Generate a range of letters. -583 -584 Arguments: -585 start -- Start of the range -586 end -- End of the range -587 -588 Returns: -589 Modified Verbex object. -590 """ -591 return self._add(f"[{start}-{end}]") -592 -593 def word(self) -> Verbex: -594 """Find a word on word boundary. +562 def as_few(self) -> Verbex: +563 """Modify previous search to not be greedy. +564 +565 Returns: +566 Modified Verbex object. +567 """ +568 return self._add("?") +569 +570 @beartype +571 def number_range(self, start: int, end: int) -> Verbex: +572 """Generate a range of numbers. +573 +574 Arguments: +575 start -- Start of the range +576 end -- End of the range +577 +578 Returns: +579 Modified Verbex object. +580 """ +581 return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") +582 +583 @beartype +584 def letter_range(self, start: Char, end: Char) -> Verbex: +585 """Generate a range of letters. +586 +587 Arguments: +588 start -- Start of the range +589 end -- End of the range +590 +591 Returns: +592 Modified Verbex object. +593 """ +594 return self._add(f"[{start}-{end}]") 595 -596 Returns: -597 Modified Verbex object. -598 """ -599 return self._add("(\\b\\w+\\b)") -600 -601 # # --------------- modifiers ------------------------ -602 -603 def with_any_case(self) -> Verbex: -604 """Modify Verbex object to be case insensitive. +596 def word(self) -> Verbex: +597 """Find a word on word boundary. +598 +599 Returns: +600 Modified Verbex object. +601 """ +602 return self._add("(\\b\\w+\\b)") +603 +604 # # --------------- modifiers ------------------------ 605 -606 Returns: -607 Modified Verbex object. -608 """ -609 self._modifiers |= re.IGNORECASE -610 return self -611 -612 def search_by_line(self) -> Verbex: -613 """Search each line, ^ and $ match begining and end of line respectively. +606 def with_any_case(self) -> Verbex: +607 """Modify Verbex object to be case insensitive. +608 +609 Returns: +610 Modified Verbex object. +611 """ +612 self._modifiers |= re.IGNORECASE +613 return self 614 -615 Returns: -616 Modified Verbex object. -617 """ -618 self._modifiers |= re.MULTILINE -619 return self -620 -621 def with_ascii(self) -> Verbex: -622 """Match ascii instead of unicode. +615 def search_by_line(self) -> Verbex: +616 """Search each line, ^ and $ match begining and end of line respectively. +617 +618 Returns: +619 Modified Verbex object. +620 """ +621 self._modifiers |= re.MULTILINE +622 return self 623 -624 Returns: -625 Modified Verbex object. -626 """ -627 self._modifiers |= re.ASCII -628 return self -629 -630 -631# left over notes from original version -632# def __getattr__(self, attr): -633# """ any other function will be sent to the regex object """ -634# regex = self.regex() -635# return getattr(regex, attr) -636 -637# def replace(self, string, repl): -638# return self.sub(repl, string) +624 def with_ascii(self) -> Verbex: +625 """Match ascii instead of unicode. +626 +627 Returns: +628 Modified Verbex object. +629 """ +630 self._modifiers |= re.ASCII +631 return self +632 +633 +634# left over notes from original version +635# def __getattr__(self, attr): +636# """ any other function will be sent to the regex object """ +637# regex = self.regex() +638# return getattr(regex, attr) 639 -640 -641if __name__ == "__main__": -642 pass +640# def replace(self, string, repl): +641# return self.sub(repl, string) +642 +643 +644if __name__ == "__main__": +645 pass @@ -913,17 +916,17 @@

View Source -
49@runtime_checkable
-50class HasIter(Protocol):
-51    """Workaround for mypy P.args."""
-52
-53    def __iter__(self) -> Iterator[Any]:
-54        """Object can be iterated.
+            
52@runtime_checkable
+53class HasIter(Protocol):
+54    """Workaround for mypy P.args."""
 55
-56        Yields:
-57            Next object.
-58        """
-59        ...
+56    def __iter__(self) -> Iterator[Any]:
+57        """Object can be iterated.
+58
+59        Yields:
+60            Next object.
+61        """
+62        ...
 
@@ -988,17 +991,17 @@

View Source -
64@runtime_checkable
-65class HasItems(Protocol):
-66    """Workaround for mypy P.kwargs."""
-67
-68    def items(self) -> Tuple[str, Any]:
-69        """Object has items method.
+            
67@runtime_checkable
+68class HasItems(Protocol):
+69    """Workaround for mypy P.kwargs."""
 70
-71        Returns:
-72            The dict of items.
-73        """
-74        ...
+71    def items(self) -> Tuple[str, Any]:
+72        """Object has items method.
+73
+74        Returns:
+75            The dict of items.
+76        """
+77        ...
 
@@ -1060,13 +1063,13 @@

View Source -
68    def items(self) -> Tuple[str, Any]:
-69        """Object has items method.
-70
-71        Returns:
-72            The dict of items.
-73        """
-74        ...
+            
71    def items(self) -> Tuple[str, Any]:
+72        """Object has items method.
+73
+74        Returns:
+75            The dict of items.
+76        """
+77        ...
 
@@ -1091,23 +1094,23 @@

View Source -
77class EscapedText(str):
-78    """Text that has been escaped for regex.
-79
-80    Arguments:
-81        str -- Extend the string class.
-82    """
-83
-84    def __new__(cls, value: str) -> EscapedText:
-85        """Return a escaped regex string.
+            
80class EscapedText(str):
+81    """Text that has been escaped for regex.
+82
+83    Arguments:
+84        str -- Extend the string class.
+85    """
 86
-87        Arguments:
-88            value -- the string to escape
+87    def __new__(cls, value: str) -> EscapedText:
+88        """Return a escaped regex string.
 89
-90        Returns:
-91            _description_
-92        """
-93        return str.__new__(cls, re.escape(value))
+90        Arguments:
+91            value -- the string to escape
+92
+93        Returns:
+94            _description_
+95        """
+96        return str.__new__(cls, re.escape(value))
 
@@ -1128,16 +1131,16 @@

View Source -
84    def __new__(cls, value: str) -> EscapedText:
-85        """Return a escaped regex string.
-86
-87        Arguments:
-88            value -- the string to escape
+            
87    def __new__(cls, value: str) -> EscapedText:
+88        """Return a escaped regex string.
 89
-90        Returns:
-91            _description_
-92        """
-93        return str.__new__(cls, re.escape(value))
+90        Arguments:
+91            value -- the string to escape
+92
+93        Returns:
+94            _description_
+95        """
+96        return str.__new__(cls, re.escape(value))
 
@@ -1221,35 +1224,35 @@

Inherited Members
View Source -
 96def re_escape(func: Callable[P, R]) -> Callable[P, R]:
- 97    """Automatically escape any string parameters as EscapedText.
- 98
- 99    Arguments:
-100        func -- The function to decorate.
+            
 99def re_escape(func: Callable[P, R]) -> Callable[P, R]:
+100    """Automatically escape any string parameters as EscapedText.
 101
-102    Returns:
-103        The decorated function.
-104    """
-105
-106    @wraps(func)
-107    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-108        escaped_args: List[Any] = []
-109        escaped_kwargs: Dict[str, Any] = {}
-110        for arg in cast(HasIter, args):
-111            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-112                escaped_args.append(EscapedText(arg))
-113            else:
-114                escaped_args.append(arg)
-115        arg_k: str
-116        arg_v: Any
-117        for arg_k, arg_v in cast(HasItems, kwargs).items():
-118            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-119                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-120            else:
-121                escaped_kwargs[arg_k] = arg_v
-122        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-123
-124    return inner
+102    Arguments:
+103        func -- The function to decorate.
+104
+105    Returns:
+106        The decorated function.
+107    """
+108
+109    @wraps(func)
+110    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
+111        escaped_args: List[Any] = []
+112        escaped_kwargs: Dict[str, Any] = {}
+113        for arg in cast(HasIter, args):
+114            if not isinstance(arg, EscapedText) and isinstance(arg, str):
+115                escaped_args.append(EscapedText(arg))
+116            else:
+117                escaped_args.append(arg)
+118        arg_k: str
+119        arg_v: Any
+120        for arg_k, arg_v in cast(HasItems, kwargs).items():
+121            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
+122                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
+123            else:
+124                escaped_kwargs[arg_k] = arg_v
+125        return func(*escaped_args, **escaped_kwargs)  # type: ignore
+126
+127    return inner
 
@@ -1276,27 +1279,27 @@
Inherited Members
View Source -
127class CharClass(Enum):
-128    """Enum of character classes in regex.
-129
-130    Arguments:
-131        Enum -- Extends the Enum class.
-132    """
-133
-134    DIGIT = "\\d"
-135    LETTER = "\\w"
-136    UPPERCASE_LETTER = "\\u"
-137    LOWERCASE_LETTER = "\\l"
-138    WHITESPACE = "\\s"
-139    TAB = "\\t"
-140
-141    def __str__(self) -> str:
-142        """To string method based on Enum value.
+            
130class CharClass(Enum):
+131    """Enum of character classes in regex.
+132
+133    Arguments:
+134        Enum -- Extends the Enum class.
+135    """
+136
+137    DIGIT = "\\d"
+138    LETTER = "\\w"
+139    UPPERCASE_LETTER = "\\u"
+140    LOWERCASE_LETTER = "\\l"
+141    WHITESPACE = "\\s"
+142    TAB = "\\t"
 143
-144        Returns:
-145            value of Enum
-146        """
-147        return self.value
+144    def __str__(self) -> str:
+145        """To string method based on Enum value.
+146
+147        Returns:
+148            value of Enum
+149        """
+150        return self.value
 
@@ -1390,26 +1393,26 @@
Inherited Members
View Source -
150class SpecialChar(Enum):
-151    """Enum of special charaters, shorthand.
-152
-153    Arguments:
-154        Enum -- Extends the Enum class.
-155    """
-156
-157    # does not work  / should not be used in [ ]
-158    LINEBREAK = "(\\n|(\\r\\n))"
-159    START_OF_LINE = "^"
-160    END_OF_LINE = "$"
-161    TAB = "\t"
-162
-163    def __str__(self) -> str:
-164        """To string for special chars enum.
+            
153class SpecialChar(Enum):
+154    """Enum of special charaters, shorthand.
+155
+156    Arguments:
+157        Enum -- Extends the Enum class.
+158    """
+159
+160    # does not work  / should not be used in [ ]
+161    LINEBREAK = "(\\n|(\\r\\n))"
+162    START_OF_LINE = "^"
+163    END_OF_LINE = "$"
+164    TAB = "\t"
 165
-166        Returns:
-167            Return value of enum as string.
-168        """
-169        return self.value
+166    def __str__(self) -> str:
+167        """To string for special chars enum.
+168
+169        Returns:
+170            Return value of enum as string.
+171        """
+172        return self.value
 
@@ -1513,439 +1516,439 @@
Inherited Members
View Source -
197class Verbex:
-198    """
-199    VerbalExpressions class.
-200
-201    the following methods do not try to match the original js lib!
-202    """
+            
200class Verbex:
+201    """
+202    VerbalExpressions class.
 203
-204    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-205
-206    @re_escape
-207    @beartype
-208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-209        """Create a Verbex object; setting any needed flags.
-210
-211        Keyword Arguments:
-212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-213        """
-214        # self._parts: List[str] = [text]
-215        self._parts: List[str] = []
-216        self._modifiers = modifiers
-217
-218    @property
-219    def modifiers(self) -> re.RegexFlag:
-220        """Return the modifiers for this Verbex object.
-221
-222        Returns:
-223            The modifiers applied to this object.
-224        """
-225        return self._modifiers
-226
-227    def __str__(self) -> str:
-228        """Return regex string representation."""
-229        return "".join(self._parts)
-230
-231    @beartype
-232    def _add(self, value: Union[str, List[str]]) -> Verbex:
-233        """
-234        Append a transformed value to internal expression to be compiled.
-235
-236        As possible, this method should be "private".
-237        """
-238        if isinstance(value, list):
-239            self._parts.extend(value)
-240        else:
-241            self._parts.append(value)
-242        return self
-243
-244    def regex(self) -> Pattern[str]:
-245        """Get a regular expression object."""
-246        return re.compile(
-247            str(self),
-248            self._modifiers,
-249        )
-250
-251    # allow VerbexEscapedCharClassOrSpecial
-252
-253    @re_escape
-254    @beartype
-255    def _capture_group_with_name(
-256        self,
-257        name: str,
-258        text: VerbexEscapedCharClassOrSpecial,
-259    ) -> Verbex:
-260        return self._add(f"(?<{name}>{str(text)})")
-261
-262    @re_escape
-263    @beartype
-264    def _capture_group_without_name(
-265        self,
-266        text: VerbexEscapedCharClassOrSpecial,
-267    ) -> Verbex:
-268        return self._add(f"({str(text)})")
-269
-270    @re_escape
-271    @beartype
-272    @_poseur_decorator("self")
-273    def capture_group(
-274        self,
-275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-277    ) -> Verbex:
-278        """Create a capture group.
-279
-280        Name is optional if not specified then the first argument is the text.
-281
-282        Keyword Arguments:
-283            name_or_text -- The name of the group / text to search for (default: {None})
-284            text -- The text to search for (default: {None})
-285
-286        Raises:
-287            ValueError: If name is specified then text must be as well.
+204    the following methods do not try to match the original js lib!
+205    """
+206
+207    EMPTY_REGEX_FLAG = re.RegexFlag(0)
+208
+209    @re_escape
+210    @beartype
+211    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+212        """Create a Verbex object; setting any needed flags.
+213
+214        Keyword Arguments:
+215            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+216        """
+217        # self._parts: List[str] = [text]
+218        self._parts: List[str] = []
+219        self._modifiers = modifiers
+220
+221    @property
+222    def modifiers(self) -> re.RegexFlag:
+223        """Return the modifiers for this Verbex object.
+224
+225        Returns:
+226            The modifiers applied to this object.
+227        """
+228        return self._modifiers
+229
+230    def __str__(self) -> str:
+231        """Return regex string representation."""
+232        return "".join(self._parts)
+233
+234    @beartype
+235    def _add(self, value: Union[str, List[str]]) -> Verbex:
+236        """
+237        Append a transformed value to internal expression to be compiled.
+238
+239        As possible, this method should be "private".
+240        """
+241        if isinstance(value, list):
+242            self._parts.extend(value)
+243        else:
+244            self._parts.append(value)
+245        return self
+246
+247    def regex(self) -> Pattern[str]:
+248        """Get a regular expression object."""
+249        return re.compile(
+250            str(self),
+251            self._modifiers,
+252        )
+253
+254    # allow VerbexEscapedCharClassOrSpecial
+255
+256    @re_escape
+257    @beartype
+258    def _capture_group_with_name(
+259        self,
+260        name: str,
+261        text: VerbexEscapedCharClassOrSpecial,
+262    ) -> Verbex:
+263        return self._add(f"(?<{name}>{str(text)})")
+264
+265    @re_escape
+266    @beartype
+267    def _capture_group_without_name(
+268        self,
+269        text: VerbexEscapedCharClassOrSpecial,
+270    ) -> Verbex:
+271        return self._add(f"({str(text)})")
+272
+273    @re_escape
+274    @beartype
+275    @_poseur_decorator("self")
+276    def capture_group(
+277        self,
+278        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+279        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+280    ) -> Verbex:
+281        """Create a capture group.
+282
+283        Name is optional if not specified then the first argument is the text.
+284
+285        Keyword Arguments:
+286            name_or_text -- The name of the group / text to search for (default: {None})
+287            text -- The text to search for (default: {None})
 288
-289        Returns:
-290            Verbex with added capture group.
-291        """
-292        if name_or_text is not None:
-293            if text is None:
-294                _text = name_or_text
-295                return self._capture_group_without_name(_text)
-296            if isinstance(name_or_text, str):
-297                return self._capture_group_with_name(name_or_text, text)
-298        raise ValueError("text must be specified with optional name")
-299
-300    @re_escape
-301    @beartype
-302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-303        """`or` is a python keyword so we use `OR` instead.
-304
-305        Arguments:
-306            text -- Text to find or a Verbex object.
+289        Raises:
+290            ValueError: If name is specified then text must be as well.
+291
+292        Returns:
+293            Verbex with added capture group.
+294        """
+295        if name_or_text is not None:
+296            if text is None:
+297                _text = name_or_text
+298                return self._capture_group_without_name(_text)
+299            if isinstance(name_or_text, str):
+300                return self._capture_group_with_name(name_or_text, text)
+301        raise ValueError("text must be specified with optional name")
+302
+303    @re_escape
+304    @beartype
+305    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa: N802
+306        """`or` is a python keyword so we use `OR` instead.
 307
-308        Returns:
-309            Modified Verbex object.
-310        """
-311        return self._add("|").find(text)
-312
-313    @re_escape
-314    @beartype
-315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-316        """Find the text or Verbex object zero or more times.
-317
-318        Arguments:
-319            text -- The text / Verbex object to look for.
+308        Arguments:
+309            text -- Text to find or a Verbex object.
+310
+311        Returns:
+312            Modified Verbex object.
+313        """
+314        return self._add("|").find(text)
+315
+316    @re_escape
+317    @beartype
+318    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+319        """Find the text or Verbex object zero or more times.
 320
-321        Returns:
-322            Modified Verbex object.
-323        """
-324        return self._add(f"(?:{str(text)})*")
-325
-326    @re_escape
-327    @beartype
-328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-329        """Find the text or Verbex object one or more times.
-330
-331        Arguments:
-332            text -- The text / Verbex object to look for.
+321        Arguments:
+322            text -- The text / Verbex object to look for.
+323
+324        Returns:
+325            Modified Verbex object.
+326        """
+327        return self._add(f"(?:{str(text)})*")
+328
+329    @re_escape
+330    @beartype
+331    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+332        """Find the text or Verbex object one or more times.
 333
-334        Returns:
-335            Modified Verbex object.
-336        """
-337        return self._add(f"(?:{str(text)})+")
-338
-339    @re_escape
-340    @beartype
-341    def n_times(
-342        self,
-343        text: VerbexEscapedCharClassOrSpecial,
-344        n: int,  # noqa: VNE001
-345    ) -> Verbex:
-346        """Find the text or Verbex object n or more times.
-347
-348        Arguments:
-349            text -- The text / Verbex object to look for.
+334        Arguments:
+335            text -- The text / Verbex object to look for.
+336
+337        Returns:
+338            Modified Verbex object.
+339        """
+340        return self._add(f"(?:{str(text)})+")
+341
+342    @re_escape
+343    @beartype
+344    def n_times(
+345        self,
+346        text: VerbexEscapedCharClassOrSpecial,
+347        n: int,  # noqa: VNE001
+348    ) -> Verbex:
+349        """Find the text or Verbex object n or more times.
 350
-351        Returns:
-352            Modified Verbex object.
-353        """
-354        return self._add(f"(?:{str(text)}){{{n}}}")
-355
-356    @re_escape
-357    @beartype
-358    def n_times_or_more(
-359        self,
-360        text: VerbexEscapedCharClassOrSpecial,
-361        n: int,  # noqa: VNE001
-362    ) -> Verbex:
-363        """Find the text or Verbex object at least n times.
-364
-365        Arguments:
-366            text -- The text / Verbex object to look for.
+351        Arguments:
+352            text -- The text / Verbex object to look for.
+353
+354        Returns:
+355            Modified Verbex object.
+356        """
+357        return self._add(f"(?:{str(text)}){{{n}}}")
+358
+359    @re_escape
+360    @beartype
+361    def n_times_or_more(
+362        self,
+363        text: VerbexEscapedCharClassOrSpecial,
+364        n: int,  # noqa: VNE001
+365    ) -> Verbex:
+366        """Find the text or Verbex object at least n times.
 367
-368        Returns:
-369            Modified Verbex object.
-370        """
-371        return self._add(f"(?:{str(text)}){{{n},}}")
-372
-373    @re_escape
-374    @beartype
-375    def n_to_m_times(
-376        self,
-377        text: VerbexEscapedCharClassOrSpecial,
-378        n: int,  # noqa: VNE001
-379        m: int,  # noqa: VNE001
-380    ) -> Verbex:
-381        """Find the text or Verbex object between n and m times.
-382
-383        Arguments:
-384            text -- The text / Verbex object to look for.
+368        Arguments:
+369            text -- The text / Verbex object to look for.
+370
+371        Returns:
+372            Modified Verbex object.
+373        """
+374        return self._add(f"(?:{str(text)}){{{n},}}")
+375
+376    @re_escape
+377    @beartype
+378    def n_to_m_times(
+379        self,
+380        text: VerbexEscapedCharClassOrSpecial,
+381        n: int,  # noqa: VNE001
+382        m: int,  # noqa: VNE001
+383    ) -> Verbex:
+384        """Find the text or Verbex object between n and m times.
 385
-386        Returns:
-387            Modified Verbex object.
-388        """
-389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-390
-391    @re_escape
-392    @beartype
-393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-394        """Possibly find the text / Verbex object.
-395
-396        Arguments:
-397            text -- The text / Verbex object to possibly find.
+386        Arguments:
+387            text -- The text / Verbex object to look for.
+388
+389        Returns:
+390            Modified Verbex object.
+391        """
+392        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+393
+394    @re_escape
+395    @beartype
+396    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+397        """Possibly find the text / Verbex object.
 398
-399        Returns:
-400            Modified Verbex object.
-401        """
-402        return self._add(f"(?:{str(text)})?")
-403
-404    @re_escape
-405    @beartype
-406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-407        """Find the text or Verbex object.
-408
-409        Arguments:
-410            text -- The text / Verbex object to look for.
+399        Arguments:
+400            text -- The text / Verbex object to possibly find.
+401
+402        Returns:
+403            Modified Verbex object.
+404        """
+405        return self._add(f"(?:{str(text)})?")
+406
+407    @re_escape
+408    @beartype
+409    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+410        """Find the text or Verbex object.
 411
-412        Returns:
-413            Modified Verbex object.
-414        """
-415        return self._add(str(text))
-416
-417    @re_escape
-418    @beartype
-419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-420        """Synonym for find.
-421
-422        Arguments:
-423            text -- The text / Verbex object to look for.
+412        Arguments:
+413            text -- The text / Verbex object to look for.
+414
+415        Returns:
+416            Modified Verbex object.
+417        """
+418        return self._add(str(text))
+419
+420    @re_escape
+421    @beartype
+422    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+423        """Synonym for find.
 424
-425        Returns:
-426            Modified Verbex object.
-427        """
-428        return self.find(text)
-429
-430    @re_escape
-431    @beartype
-432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-433        """Match if string is followed by text.
-434
-435        Positive lookahead
-436
-437        Returns:
-438            Modified Verbex object.
-439        """
-440        return self._add(f"(?={text})")
-441
-442    @re_escape
-443    @beartype
-444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-445        """Match if string is not followed by text.
-446
-447        Negative lookahead
-448
-449        Returns:
-450            Modified Verbex object.
-451        """
-452        return self._add(f"(?!{text})")
-453
-454    @re_escape
-455    @beartype
-456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-457        """Match if string is not preceded by text.
-458
-459        Positive lookbehind
-460
-461        Returns:
-462            Modified Verbex object.
-463        """
-464        return self._add(f"(?<={text})")
-465
-466    @re_escape
-467    @beartype
-468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-469        """Match if string is not preceded by text.
-470
-471        Negative Lookbehind
-472
-473        Returns:
-474            Modified Verbex object.
-475        """
-476        return self._add(f"(?<!{text})")
-477
-478    # only allow CharclassOrChars
-479
-480    @re_escape
-481    @beartype
-482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-483        """Find anything in this group of chars or char class.
-484
-485        Arguments:
-486            text -- The characters to look for.
+425        Arguments:
+426            text -- The text / Verbex object to look for.
+427
+428        Returns:
+429            Modified Verbex object.
+430        """
+431        return self.find(text)
+432
+433    @re_escape
+434    @beartype
+435    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+436        """Match if string is followed by text.
+437
+438        Positive lookahead
+439
+440        Returns:
+441            Modified Verbex object.
+442        """
+443        return self._add(f"(?={text})")
+444
+445    @re_escape
+446    @beartype
+447    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+448        """Match if string is not followed by text.
+449
+450        Negative lookahead
+451
+452        Returns:
+453            Modified Verbex object.
+454        """
+455        return self._add(f"(?!{text})")
+456
+457    @re_escape
+458    @beartype
+459    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+460        """Match if string is not preceded by text.
+461
+462        Positive lookbehind
+463
+464        Returns:
+465            Modified Verbex object.
+466        """
+467        return self._add(f"(?<={text})")
+468
+469    @re_escape
+470    @beartype
+471    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+472        """Match if string is not preceded by text.
+473
+474        Negative Lookbehind
+475
+476        Returns:
+477            Modified Verbex object.
+478        """
+479        return self._add(f"(?<!{text})")
+480
+481    # only allow CharclassOrChars
+482
+483    @re_escape
+484    @beartype
+485    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+486        """Find anything in this group of chars or char class.
 487
-488        Returns:
-489            Modified Verbex object.
-490        """
-491        return self._add(f"(?:[{chargroup}])")
-492
-493    @re_escape
-494    @beartype
-495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-496        """Find anything but this group of chars or char class.
-497
-498        Arguments:
-499            text -- The characters to not look for.
+488        Arguments:
+489            text -- The characters to look for.
+490
+491        Returns:
+492            Modified Verbex object.
+493        """
+494        return self._add(f"(?:[{chargroup}])")
+495
+496    @re_escape
+497    @beartype
+498    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+499        """Find anything but this group of chars or char class.
 500
-501        Returns:
-502            Modified Verbex object.
-503        """
-504        return self._add(f"(?:[^{text}])")
-505
-506    @re_escape
-507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-508        """Find anything one or more times but this group of chars or char class.
-509
-510        Arguments:
-511            text -- The characters to not look for.
+501        Arguments:
+502            text -- The characters to not look for.
+503
+504        Returns:
+505            Modified Verbex object.
+506        """
+507        return self._add(f"(?:[^{text}])")
+508
+509    @re_escape
+510    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+511        """Find anything one or more times but this group of chars or char class.
 512
-513        Returns:
-514            Modified Verbex object.
-515        """
-516        return self._add(f"[^{chargroup}]+")
-517
-518    # no text input
-519
-520    def start_of_line(self) -> Verbex:
-521        """Find the start of the line.
+513        Arguments:
+514            text -- The characters to not look for.
+515
+516        Returns:
+517            Modified Verbex object.
+518        """
+519        return self._add(f"[^{chargroup}]+")
+520
+521    # no text input
 522
-523        Returns:
-524            Modified Verbex object.
-525        """
-526        return self.find(SpecialChar.START_OF_LINE)
-527
-528    def end_of_line(self) -> Verbex:
-529        """Find the end of the line.
+523    def start_of_line(self) -> Verbex:
+524        """Find the start of the line.
+525
+526        Returns:
+527            Modified Verbex object.
+528        """
+529        return self.find(SpecialChar.START_OF_LINE)
 530
-531        Returns:
-532            Modified Verbex object.
-533        """
-534        return self.find(SpecialChar.END_OF_LINE)
-535
-536    def line_break(self) -> Verbex:
-537        """Find a line break.
+531    def end_of_line(self) -> Verbex:
+532        """Find the end of the line.
+533
+534        Returns:
+535            Modified Verbex object.
+536        """
+537        return self.find(SpecialChar.END_OF_LINE)
 538
-539        Returns:
-540            Modified Verbex object.
-541        """
-542        return self.find(SpecialChar.LINEBREAK)
-543
-544    def tab(self) -> Verbex:
-545        """Find a tab.
+539    def line_break(self) -> Verbex:
+540        """Find a line break.
+541
+542        Returns:
+543            Modified Verbex object.
+544        """
+545        return self.find(SpecialChar.LINEBREAK)
 546
-547        Returns:
-548            Modified Verbex object.
-549        """
-550        return self.find(SpecialChar.TAB)
-551
-552    def anything(self) -> Verbex:
-553        """Find anything one or more time.
+547    def tab(self) -> Verbex:
+548        """Find a tab.
+549
+550        Returns:
+551            Modified Verbex object.
+552        """
+553        return self.find(SpecialChar.TAB)
 554
-555        Returns:
-556            Modified Verbex object.
-557        """
-558        return self._add(".+")
-559
-560    def as_few(self) -> Verbex:
-561        """Modify previous search to not be greedy.
+555    def anything(self) -> Verbex:
+556        """Find anything one or more time.
+557
+558        Returns:
+559            Modified Verbex object.
+560        """
+561        return self._add(".+")
 562
-563        Returns:
-564            Modified Verbex object.
-565        """
-566        return self._add("?")
-567
-568    @beartype
-569    def number_range(self, start: int, end: int) -> Verbex:
-570        """Generate a range of numbers.
-571
-572        Arguments:
-573            start -- Start of the range
-574            end -- End of the range
-575
-576        Returns:
-577            Modified Verbex object.
-578        """
-579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-580
-581    @beartype
-582    def letter_range(self, start: Char, end: Char) -> Verbex:
-583        """Generate a range of letters.
-584
-585        Arguments:
-586            start -- Start of the range
-587            end -- End of the range
-588
-589        Returns:
-590            Modified Verbex object.
-591        """
-592        return self._add(f"[{start}-{end}]")
-593
-594    def word(self) -> Verbex:
-595        """Find a word on word boundary.
+563    def as_few(self) -> Verbex:
+564        """Modify previous search to not be greedy.
+565
+566        Returns:
+567            Modified Verbex object.
+568        """
+569        return self._add("?")
+570
+571    @beartype
+572    def number_range(self, start: int, end: int) -> Verbex:
+573        """Generate a range of numbers.
+574
+575        Arguments:
+576            start -- Start of the range
+577            end -- End of the range
+578
+579        Returns:
+580            Modified Verbex object.
+581        """
+582        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+583
+584    @beartype
+585    def letter_range(self, start: Char, end: Char) -> Verbex:
+586        """Generate a range of letters.
+587
+588        Arguments:
+589            start -- Start of the range
+590            end -- End of the range
+591
+592        Returns:
+593            Modified Verbex object.
+594        """
+595        return self._add(f"[{start}-{end}]")
 596
-597        Returns:
-598            Modified Verbex object.
-599        """
-600        return self._add("(\\b\\w+\\b)")
-601
-602    # # --------------- modifiers ------------------------
-603
-604    def with_any_case(self) -> Verbex:
-605        """Modify Verbex object to be case insensitive.
+597    def word(self) -> Verbex:
+598        """Find a word on word boundary.
+599
+600        Returns:
+601            Modified Verbex object.
+602        """
+603        return self._add("(\\b\\w+\\b)")
+604
+605    # # --------------- modifiers ------------------------
 606
-607        Returns:
-608            Modified Verbex object.
-609        """
-610        self._modifiers |= re.IGNORECASE
-611        return self
-612
-613    def search_by_line(self) -> Verbex:
-614        """Search each line, ^ and $ match begining and end of line respectively.
+607    def with_any_case(self) -> Verbex:
+608        """Modify Verbex object to be case insensitive.
+609
+610        Returns:
+611            Modified Verbex object.
+612        """
+613        self._modifiers |= re.IGNORECASE
+614        return self
 615
-616        Returns:
-617            Modified Verbex object.
-618        """
-619        self._modifiers |= re.MULTILINE
-620        return self
-621
-622    def with_ascii(self) -> Verbex:
-623        """Match ascii instead of unicode.
+616    def search_by_line(self) -> Verbex:
+617        """Search each line, ^ and $ match begining and end of line respectively.
+618
+619        Returns:
+620            Modified Verbex object.
+621        """
+622        self._modifiers |= re.MULTILINE
+623        return self
 624
-625        Returns:
-626            Modified Verbex object.
-627        """
-628        self._modifiers |= re.ASCII
-629        return self
+625    def with_ascii(self) -> Verbex:
+626        """Match ascii instead of unicode.
+627
+628        Returns:
+629            Modified Verbex object.
+630        """
+631        self._modifiers |= re.ASCII
+632        return self
 
@@ -1967,17 +1970,17 @@
Inherited Members
View Source -
206    @re_escape
-207    @beartype
-208    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-209        """Create a Verbex object; setting any needed flags.
-210
-211        Keyword Arguments:
-212            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-213        """
-214        # self._parts: List[str] = [text]
-215        self._parts: List[str] = []
-216        self._modifiers = modifiers
+            
209    @re_escape
+210    @beartype
+211    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
+212        """Create a Verbex object; setting any needed flags.
+213
+214        Keyword Arguments:
+215            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
+216        """
+217        # self._parts: List[str] = [text]
+218        self._parts: List[str] = []
+219        self._modifiers = modifiers
 
@@ -2025,12 +2028,12 @@
Inherited Members
View Source -
244    def regex(self) -> Pattern[str]:
-245        """Get a regular expression object."""
-246        return re.compile(
-247            str(self),
-248            self._modifiers,
-249        )
+            
247    def regex(self) -> Pattern[str]:
+248        """Get a regular expression object."""
+249        return re.compile(
+250            str(self),
+251            self._modifiers,
+252        )
 
@@ -2056,35 +2059,35 @@
Inherited Members
View Source -
270    @re_escape
-271    @beartype
-272    @_poseur_decorator("self")
-273    def capture_group(
-274        self,
-275        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-276        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-277    ) -> Verbex:
-278        """Create a capture group.
-279
-280        Name is optional if not specified then the first argument is the text.
-281
-282        Keyword Arguments:
-283            name_or_text -- The name of the group / text to search for (default: {None})
-284            text -- The text to search for (default: {None})
-285
-286        Raises:
-287            ValueError: If name is specified then text must be as well.
+            
273    @re_escape
+274    @beartype
+275    @_poseur_decorator("self")
+276    def capture_group(
+277        self,
+278        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
+279        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
+280    ) -> Verbex:
+281        """Create a capture group.
+282
+283        Name is optional if not specified then the first argument is the text.
+284
+285        Keyword Arguments:
+286            name_or_text -- The name of the group / text to search for (default: {None})
+287            text -- The text to search for (default: {None})
 288
-289        Returns:
-290            Verbex with added capture group.
-291        """
-292        if name_or_text is not None:
-293            if text is None:
-294                _text = name_or_text
-295                return self._capture_group_without_name(_text)
-296            if isinstance(name_or_text, str):
-297                return self._capture_group_with_name(name_or_text, text)
-298        raise ValueError("text must be specified with optional name")
+289        Raises:
+290            ValueError: If name is specified then text must be as well.
+291
+292        Returns:
+293            Verbex with added capture group.
+294        """
+295        if name_or_text is not None:
+296            if text is None:
+297                _text = name_or_text
+298                return self._capture_group_without_name(_text)
+299            if isinstance(name_or_text, str):
+300                return self._capture_group_with_name(name_or_text, text)
+301        raise ValueError("text must be specified with optional name")
 
@@ -2121,18 +2124,18 @@
Inherited Members
View Source -
300    @re_escape
-301    @beartype
-302    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa N802
-303        """`or` is a python keyword so we use `OR` instead.
-304
-305        Arguments:
-306            text -- Text to find or a Verbex object.
+            
303    @re_escape
+304    @beartype
+305    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa: N802
+306        """`or` is a python keyword so we use `OR` instead.
 307
-308        Returns:
-309            Modified Verbex object.
-310        """
-311        return self._add("|").find(text)
+308        Arguments:
+309            text -- Text to find or a Verbex object.
+310
+311        Returns:
+312            Modified Verbex object.
+313        """
+314        return self._add("|").find(text)
 
@@ -2163,18 +2166,18 @@
Inherited Members
View Source -
313    @re_escape
-314    @beartype
-315    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-316        """Find the text or Verbex object zero or more times.
-317
-318        Arguments:
-319            text -- The text / Verbex object to look for.
+            
316    @re_escape
+317    @beartype
+318    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+319        """Find the text or Verbex object zero or more times.
 320
-321        Returns:
-322            Modified Verbex object.
-323        """
-324        return self._add(f"(?:{str(text)})*")
+321        Arguments:
+322            text -- The text / Verbex object to look for.
+323
+324        Returns:
+325            Modified Verbex object.
+326        """
+327        return self._add(f"(?:{str(text)})*")
 
@@ -2205,18 +2208,18 @@
Inherited Members
View Source -
326    @re_escape
-327    @beartype
-328    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-329        """Find the text or Verbex object one or more times.
-330
-331        Arguments:
-332            text -- The text / Verbex object to look for.
+            
329    @re_escape
+330    @beartype
+331    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+332        """Find the text or Verbex object one or more times.
 333
-334        Returns:
-335            Modified Verbex object.
-336        """
-337        return self._add(f"(?:{str(text)})+")
+334        Arguments:
+335            text -- The text / Verbex object to look for.
+336
+337        Returns:
+338            Modified Verbex object.
+339        """
+340        return self._add(f"(?:{str(text)})+")
 
@@ -2248,22 +2251,22 @@
Inherited Members
View Source -
339    @re_escape
-340    @beartype
-341    def n_times(
-342        self,
-343        text: VerbexEscapedCharClassOrSpecial,
-344        n: int,  # noqa: VNE001
-345    ) -> Verbex:
-346        """Find the text or Verbex object n or more times.
-347
-348        Arguments:
-349            text -- The text / Verbex object to look for.
+            
342    @re_escape
+343    @beartype
+344    def n_times(
+345        self,
+346        text: VerbexEscapedCharClassOrSpecial,
+347        n: int,  # noqa: VNE001
+348    ) -> Verbex:
+349        """Find the text or Verbex object n or more times.
 350
-351        Returns:
-352            Modified Verbex object.
-353        """
-354        return self._add(f"(?:{str(text)}){{{n}}}")
+351        Arguments:
+352            text -- The text / Verbex object to look for.
+353
+354        Returns:
+355            Modified Verbex object.
+356        """
+357        return self._add(f"(?:{str(text)}){{{n}}}")
 
@@ -2295,22 +2298,22 @@
Inherited Members
View Source -
356    @re_escape
-357    @beartype
-358    def n_times_or_more(
-359        self,
-360        text: VerbexEscapedCharClassOrSpecial,
-361        n: int,  # noqa: VNE001
-362    ) -> Verbex:
-363        """Find the text or Verbex object at least n times.
-364
-365        Arguments:
-366            text -- The text / Verbex object to look for.
+            
359    @re_escape
+360    @beartype
+361    def n_times_or_more(
+362        self,
+363        text: VerbexEscapedCharClassOrSpecial,
+364        n: int,  # noqa: VNE001
+365    ) -> Verbex:
+366        """Find the text or Verbex object at least n times.
 367
-368        Returns:
-369            Modified Verbex object.
-370        """
-371        return self._add(f"(?:{str(text)}){{{n},}}")
+368        Arguments:
+369            text -- The text / Verbex object to look for.
+370
+371        Returns:
+372            Modified Verbex object.
+373        """
+374        return self._add(f"(?:{str(text)}){{{n},}}")
 
@@ -2343,23 +2346,23 @@
Inherited Members
View Source -
373    @re_escape
-374    @beartype
-375    def n_to_m_times(
-376        self,
-377        text: VerbexEscapedCharClassOrSpecial,
-378        n: int,  # noqa: VNE001
-379        m: int,  # noqa: VNE001
-380    ) -> Verbex:
-381        """Find the text or Verbex object between n and m times.
-382
-383        Arguments:
-384            text -- The text / Verbex object to look for.
+            
376    @re_escape
+377    @beartype
+378    def n_to_m_times(
+379        self,
+380        text: VerbexEscapedCharClassOrSpecial,
+381        n: int,  # noqa: VNE001
+382        m: int,  # noqa: VNE001
+383    ) -> Verbex:
+384        """Find the text or Verbex object between n and m times.
 385
-386        Returns:
-387            Modified Verbex object.
-388        """
-389        return self._add(f"(?:{str(text)}){{{n},{m}}}")
+386        Arguments:
+387            text -- The text / Verbex object to look for.
+388
+389        Returns:
+390            Modified Verbex object.
+391        """
+392        return self._add(f"(?:{str(text)}){{{n},{m}}}")
 
@@ -2390,18 +2393,18 @@
Inherited Members
View Source -
391    @re_escape
-392    @beartype
-393    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-394        """Possibly find the text / Verbex object.
-395
-396        Arguments:
-397            text -- The text / Verbex object to possibly find.
+            
394    @re_escape
+395    @beartype
+396    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+397        """Possibly find the text / Verbex object.
 398
-399        Returns:
-400            Modified Verbex object.
-401        """
-402        return self._add(f"(?:{str(text)})?")
+399        Arguments:
+400            text -- The text / Verbex object to possibly find.
+401
+402        Returns:
+403            Modified Verbex object.
+404        """
+405        return self._add(f"(?:{str(text)})?")
 
@@ -2432,18 +2435,18 @@
Inherited Members
View Source -
404    @re_escape
-405    @beartype
-406    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-407        """Find the text or Verbex object.
-408
-409        Arguments:
-410            text -- The text / Verbex object to look for.
+            
407    @re_escape
+408    @beartype
+409    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+410        """Find the text or Verbex object.
 411
-412        Returns:
-413            Modified Verbex object.
-414        """
-415        return self._add(str(text))
+412        Arguments:
+413            text -- The text / Verbex object to look for.
+414
+415        Returns:
+416            Modified Verbex object.
+417        """
+418        return self._add(str(text))
 
@@ -2474,18 +2477,18 @@
Inherited Members
View Source -
417    @re_escape
-418    @beartype
-419    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-420        """Synonym for find.
-421
-422        Arguments:
-423            text -- The text / Verbex object to look for.
+            
420    @re_escape
+421    @beartype
+422    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+423        """Synonym for find.
 424
-425        Returns:
-426            Modified Verbex object.
-427        """
-428        return self.find(text)
+425        Arguments:
+426            text -- The text / Verbex object to look for.
+427
+428        Returns:
+429            Modified Verbex object.
+430        """
+431        return self.find(text)
 
@@ -2516,17 +2519,17 @@
Inherited Members
View Source -
430    @re_escape
-431    @beartype
-432    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-433        """Match if string is followed by text.
-434
-435        Positive lookahead
-436
-437        Returns:
-438            Modified Verbex object.
-439        """
-440        return self._add(f"(?={text})")
+            
433    @re_escape
+434    @beartype
+435    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+436        """Match if string is followed by text.
+437
+438        Positive lookahead
+439
+440        Returns:
+441            Modified Verbex object.
+442        """
+443        return self._add(f"(?={text})")
 
@@ -2556,17 +2559,17 @@
Inherited Members
View Source -
442    @re_escape
-443    @beartype
-444    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-445        """Match if string is not followed by text.
-446
-447        Negative lookahead
-448
-449        Returns:
-450            Modified Verbex object.
-451        """
-452        return self._add(f"(?!{text})")
+            
445    @re_escape
+446    @beartype
+447    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+448        """Match if string is not followed by text.
+449
+450        Negative lookahead
+451
+452        Returns:
+453            Modified Verbex object.
+454        """
+455        return self._add(f"(?!{text})")
 
@@ -2596,17 +2599,17 @@
Inherited Members
View Source -
454    @re_escape
-455    @beartype
-456    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-457        """Match if string is not preceded by text.
-458
-459        Positive lookbehind
-460
-461        Returns:
-462            Modified Verbex object.
-463        """
-464        return self._add(f"(?<={text})")
+            
457    @re_escape
+458    @beartype
+459    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+460        """Match if string is not preceded by text.
+461
+462        Positive lookbehind
+463
+464        Returns:
+465            Modified Verbex object.
+466        """
+467        return self._add(f"(?<={text})")
 
@@ -2636,17 +2639,17 @@
Inherited Members
View Source -
466    @re_escape
-467    @beartype
-468    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-469        """Match if string is not preceded by text.
-470
-471        Negative Lookbehind
-472
-473        Returns:
-474            Modified Verbex object.
-475        """
-476        return self._add(f"(?<!{text})")
+            
469    @re_escape
+470    @beartype
+471    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
+472        """Match if string is not preceded by text.
+473
+474        Negative Lookbehind
+475
+476        Returns:
+477            Modified Verbex object.
+478        """
+479        return self._add(f"(?<!{text})")
 
@@ -2676,18 +2679,18 @@
Inherited Members
View Source -
480    @re_escape
-481    @beartype
-482    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-483        """Find anything in this group of chars or char class.
-484
-485        Arguments:
-486            text -- The characters to look for.
+            
483    @re_escape
+484    @beartype
+485    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
+486        """Find anything in this group of chars or char class.
 487
-488        Returns:
-489            Modified Verbex object.
-490        """
-491        return self._add(f"(?:[{chargroup}])")
+488        Arguments:
+489            text -- The characters to look for.
+490
+491        Returns:
+492            Modified Verbex object.
+493        """
+494        return self._add(f"(?:[{chargroup}])")
 
@@ -2718,18 +2721,18 @@
Inherited Members
View Source -
493    @re_escape
-494    @beartype
-495    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-496        """Find anything but this group of chars or char class.
-497
-498        Arguments:
-499            text -- The characters to not look for.
+            
496    @re_escape
+497    @beartype
+498    def not_any_of(self, text: CharClassOrChars) -> Verbex:
+499        """Find anything but this group of chars or char class.
 500
-501        Returns:
-502            Modified Verbex object.
-503        """
-504        return self._add(f"(?:[^{text}])")
+501        Arguments:
+502            text -- The characters to not look for.
+503
+504        Returns:
+505            Modified Verbex object.
+506        """
+507        return self._add(f"(?:[^{text}])")
 
@@ -2759,17 +2762,17 @@
Inherited Members
View Source -
506    @re_escape
-507    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-508        """Find anything one or more times but this group of chars or char class.
-509
-510        Arguments:
-511            text -- The characters to not look for.
+            
509    @re_escape
+510    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
+511        """Find anything one or more times but this group of chars or char class.
 512
-513        Returns:
-514            Modified Verbex object.
-515        """
-516        return self._add(f"[^{chargroup}]+")
+513        Arguments:
+514            text -- The characters to not look for.
+515
+516        Returns:
+517            Modified Verbex object.
+518        """
+519        return self._add(f"[^{chargroup}]+")
 
@@ -2795,13 +2798,13 @@
Inherited Members
View Source -
520    def start_of_line(self) -> Verbex:
-521        """Find the start of the line.
-522
-523        Returns:
-524            Modified Verbex object.
-525        """
-526        return self.find(SpecialChar.START_OF_LINE)
+            
523    def start_of_line(self) -> Verbex:
+524        """Find the start of the line.
+525
+526        Returns:
+527            Modified Verbex object.
+528        """
+529        return self.find(SpecialChar.START_OF_LINE)
 
@@ -2824,13 +2827,13 @@
Inherited Members
View Source -
528    def end_of_line(self) -> Verbex:
-529        """Find the end of the line.
-530
-531        Returns:
-532            Modified Verbex object.
-533        """
-534        return self.find(SpecialChar.END_OF_LINE)
+            
531    def end_of_line(self) -> Verbex:
+532        """Find the end of the line.
+533
+534        Returns:
+535            Modified Verbex object.
+536        """
+537        return self.find(SpecialChar.END_OF_LINE)
 
@@ -2853,13 +2856,13 @@
Inherited Members
View Source -
536    def line_break(self) -> Verbex:
-537        """Find a line break.
-538
-539        Returns:
-540            Modified Verbex object.
-541        """
-542        return self.find(SpecialChar.LINEBREAK)
+            
539    def line_break(self) -> Verbex:
+540        """Find a line break.
+541
+542        Returns:
+543            Modified Verbex object.
+544        """
+545        return self.find(SpecialChar.LINEBREAK)
 
@@ -2882,13 +2885,13 @@
Inherited Members
View Source -
544    def tab(self) -> Verbex:
-545        """Find a tab.
-546
-547        Returns:
-548            Modified Verbex object.
-549        """
-550        return self.find(SpecialChar.TAB)
+            
547    def tab(self) -> Verbex:
+548        """Find a tab.
+549
+550        Returns:
+551            Modified Verbex object.
+552        """
+553        return self.find(SpecialChar.TAB)
 
@@ -2911,13 +2914,13 @@
Inherited Members
View Source -
552    def anything(self) -> Verbex:
-553        """Find anything one or more time.
-554
-555        Returns:
-556            Modified Verbex object.
-557        """
-558        return self._add(".+")
+            
555    def anything(self) -> Verbex:
+556        """Find anything one or more time.
+557
+558        Returns:
+559            Modified Verbex object.
+560        """
+561        return self._add(".+")
 
@@ -2940,13 +2943,13 @@
Inherited Members
View Source -
560    def as_few(self) -> Verbex:
-561        """Modify previous search to not be greedy.
-562
-563        Returns:
-564            Modified Verbex object.
-565        """
-566        return self._add("?")
+            
563    def as_few(self) -> Verbex:
+564        """Modify previous search to not be greedy.
+565
+566        Returns:
+567            Modified Verbex object.
+568        """
+569        return self._add("?")
 
@@ -2970,18 +2973,18 @@
Inherited Members
View Source -
568    @beartype
-569    def number_range(self, start: int, end: int) -> Verbex:
-570        """Generate a range of numbers.
-571
-572        Arguments:
-573            start -- Start of the range
-574            end -- End of the range
-575
-576        Returns:
-577            Modified Verbex object.
-578        """
-579        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
+            
571    @beartype
+572    def number_range(self, start: int, end: int) -> Verbex:
+573        """Generate a range of numbers.
+574
+575        Arguments:
+576            start -- Start of the range
+577            end -- End of the range
+578
+579        Returns:
+580            Modified Verbex object.
+581        """
+582        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
 
@@ -3013,18 +3016,18 @@
Inherited Members
View Source -
581    @beartype
-582    def letter_range(self, start: Char, end: Char) -> Verbex:
-583        """Generate a range of letters.
-584
-585        Arguments:
-586            start -- Start of the range
-587            end -- End of the range
-588
-589        Returns:
-590            Modified Verbex object.
-591        """
-592        return self._add(f"[{start}-{end}]")
+            
584    @beartype
+585    def letter_range(self, start: Char, end: Char) -> Verbex:
+586        """Generate a range of letters.
+587
+588        Arguments:
+589            start -- Start of the range
+590            end -- End of the range
+591
+592        Returns:
+593            Modified Verbex object.
+594        """
+595        return self._add(f"[{start}-{end}]")
 
@@ -3051,13 +3054,13 @@
Inherited Members
View Source -
594    def word(self) -> Verbex:
-595        """Find a word on word boundary.
-596
-597        Returns:
-598            Modified Verbex object.
-599        """
-600        return self._add("(\\b\\w+\\b)")
+            
597    def word(self) -> Verbex:
+598        """Find a word on word boundary.
+599
+600        Returns:
+601            Modified Verbex object.
+602        """
+603        return self._add("(\\b\\w+\\b)")
 
@@ -3080,14 +3083,14 @@
Inherited Members
View Source -
604    def with_any_case(self) -> Verbex:
-605        """Modify Verbex object to be case insensitive.
-606
-607        Returns:
-608            Modified Verbex object.
-609        """
-610        self._modifiers |= re.IGNORECASE
-611        return self
+            
607    def with_any_case(self) -> Verbex:
+608        """Modify Verbex object to be case insensitive.
+609
+610        Returns:
+611            Modified Verbex object.
+612        """
+613        self._modifiers |= re.IGNORECASE
+614        return self
 
@@ -3110,14 +3113,14 @@
Inherited Members
View Source -
613    def search_by_line(self) -> Verbex:
-614        """Search each line, ^ and $ match begining and end of line respectively.
-615
-616        Returns:
-617            Modified Verbex object.
-618        """
-619        self._modifiers |= re.MULTILINE
-620        return self
+            
616    def search_by_line(self) -> Verbex:
+617        """Search each line, ^ and $ match begining and end of line respectively.
+618
+619        Returns:
+620            Modified Verbex object.
+621        """
+622        self._modifiers |= re.MULTILINE
+623        return self
 
@@ -3140,14 +3143,14 @@
Inherited Members
View Source -
622    def with_ascii(self) -> Verbex:
-623        """Match ascii instead of unicode.
-624
-625        Returns:
-626            Modified Verbex object.
-627        """
-628        self._modifiers |= re.ASCII
-629        return self
+            
625    def with_ascii(self) -> Verbex:
+626        """Match ascii instead of unicode.
+627
+628        Returns:
+629            Modified Verbex object.
+630        """
+631        self._modifiers |= re.ASCII
+632        return self
 
diff --git a/verbex/verbex.py b/verbex/verbex.py index d47fd4d..1a2d9ac 100644 --- a/verbex/verbex.py +++ b/verbex/verbex.py @@ -16,7 +16,10 @@ except ImportError: from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable # type: ignore # <--- if Python < 3.9.0 # noqa E501 -from typing import Pattern, TypeVar +from typing import TYPE_CHECKING, Pattern, TypeVar + +if TYPE_CHECKING: + from typing import Protocol # noqa: F811 from beartype import beartype # type: ignore from beartype.typing import ( # type: ignore @@ -40,8 +43,8 @@ def _string_len_is_1(text: object) -> bool: Char = Annotated[str, Is[_string_len_is_1]] -P = ParamSpec("P") # noqa VNE001 -R = TypeVar("R") # noqa VNE001 +P = ParamSpec("P") # noqa: VNE001 +R = TypeVar("R") # noqa: VNE001 # work around for bug https://github.com/python/mypy/issues/12660 @@ -299,7 +302,7 @@ def capture_group( @re_escape @beartype - def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa N802 + def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 """`or` is a python keyword so we use `OR` instead. Arguments: From 7f5b797ee56f2ca8f843795e80043d1d9d8c16ef Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 01:03:30 -0400 Subject: [PATCH 64/90] cleanup and move to new dev enviroment. Drop support for python versions < 3.10 --- .gitattributes | 51 +- .github/dependabot.yml | 7 + .github/workflows/black.yaml | 24 + .github/workflows/dapperdata.yaml | 24 + .github/workflows/gh_pages.yaml | 18 + .github/workflows/main.yml | 41 - .github/workflows/mypy.yaml | 24 + .github/workflows/pip-audit.yaml | 23 + .github/workflows/pypi.yaml | 93 + .github/workflows/pytest.yaml | 27 + .github/workflows/ruff.yaml | 24 + .github/workflows/tomlsort.yaml | 27 + .github/workflows/tox.yaml | 30 + .gitignore | 169 +- .pre-commit-config.yaml | 197 +- .python-version | 1 + .yamllint | 3 + CODE_OF_CONDUCT.md | 5 + GPLv3_LICENSE.txt | 674 ------ LICENSE.txt | 687 +++++- MANIFEST.IN | 3 - builddoc.bat | 3 - check_names.py | 69 - docs/Makefile | 20 + docs/_static/css/custom.css | 9 + docs/conf.py | 32 + docs/getting-started.rst | 6 + docs/index.html | 7 - docs/index.rst | 23 + docs/make.bat | 35 + docs/search.js | 46 - docs/source/modules.rst | 7 + docs/source/verbex.rst | 13 + docs/verbex.html | 241 -- docs/verbex/verbex.html | 3346 ---------------------------- justfile | 143 ++ pyproject.toml | 323 ++- setup.cfg | 27 - setup.py | 4 - {verbex => src/verbex}/__init__.py | 0 {verbex => src/verbex}/py.typed | 0 {verbex => src/verbex}/verbex.py | 444 ++-- verbex/GPLv3_LICENSE.txt | 674 ------ verbex/LICENSE.txt | 39 - 44 files changed, 2045 insertions(+), 5618 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/black.yaml create mode 100644 .github/workflows/dapperdata.yaml create mode 100644 .github/workflows/gh_pages.yaml delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/mypy.yaml create mode 100644 .github/workflows/pip-audit.yaml create mode 100644 .github/workflows/pypi.yaml create mode 100644 .github/workflows/pytest.yaml create mode 100644 .github/workflows/ruff.yaml create mode 100644 .github/workflows/tomlsort.yaml create mode 100644 .github/workflows/tox.yaml create mode 100644 .python-version create mode 100644 CODE_OF_CONDUCT.md delete mode 100644 GPLv3_LICENSE.txt delete mode 100644 MANIFEST.IN delete mode 100644 builddoc.bat delete mode 100644 check_names.py create mode 100644 docs/Makefile create mode 100644 docs/_static/css/custom.css create mode 100644 docs/conf.py create mode 100644 docs/getting-started.rst delete mode 100644 docs/index.html create mode 100644 docs/index.rst create mode 100644 docs/make.bat delete mode 100644 docs/search.js create mode 100644 docs/source/modules.rst create mode 100644 docs/source/verbex.rst delete mode 100644 docs/verbex.html delete mode 100644 docs/verbex/verbex.html create mode 100644 justfile delete mode 100644 setup.cfg delete mode 100755 setup.py rename {verbex => src/verbex}/__init__.py (100%) rename {verbex => src/verbex}/py.typed (100%) rename {verbex => src/verbex}/verbex.py (56%) delete mode 100644 verbex/GPLv3_LICENSE.txt delete mode 100644 verbex/LICENSE.txt diff --git a/.gitattributes b/.gitattributes index 72c1b76..49f64d0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,50 +1 @@ -# .gitattributes snippet to force users to use same line endings for project. -# -# Handle line endings automatically for files detected as text -# and leave all files detected as binary untouched. -* text=auto - -# -# The above will handle all files NOT found below -# https://help.github.com/articles/dealing-with-line-endings/ -# https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes - - - -# These files are text and should be normalized (Convert crlf => lf) -*.php text -*.css text -*.js text -*.json text -*.htm text -*.html text -*.xml text -*.txt text -*.ini text -*.inc text -*.pl text -*.rb text -*.py text -*.scm text -*.sql text -.htaccess text -*.sh text - -# These files are binary and should be left untouched -# (binary is a macro for -text -diff) -*.png binary -*.jpg binary -*.jpeg binary -*.gif binary -*.ico binary -*.mov binary -*.mp4 binary -*.mp3 binary -*.flv binary -*.fla binary -*.swf binary -*.gz binary -*.zip binary -*.7z binary -*.ttf binary -*.pyc binary +stringdatadeque/_version.py export-subst diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..718572b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml new file mode 100644 index 0000000..adc6081 --- /dev/null +++ b/.github/workflows/black.yaml @@ -0,0 +1,24 @@ +name: Black Formatting + +"on": + push: + pull_request: + +jobs: + black: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Test Formatting + run: just ruff_format_fixes diff --git a/.github/workflows/dapperdata.yaml b/.github/workflows/dapperdata.yaml new file mode 100644 index 0000000..f8c2a7f --- /dev/null +++ b/.github/workflows/dapperdata.yaml @@ -0,0 +1,24 @@ +name: Configuration File Formatting + +"on": + push: + pull_request: + +jobs: + dapperdata: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Test Formatting + run: just dapperdata_check diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml new file mode 100644 index 0000000..ac6bda7 --- /dev/null +++ b/.github/workflows/gh_pages.yaml @@ -0,0 +1,18 @@ +name: Deploy Sphinx documentation to Pages + +"on": + push: + branches: [main] # branch to trigger deployment + +jobs: + pages: + runs-on: ubuntu-20.04 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + permissions: + pages: write + id-token: write + steps: + - id: deployment + uses: sphinx-notes/pages@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index a413918..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,41 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the workflow will run -on: # yamllint disable-line rule:truthy - # Triggers the workflow on push or pull request events but only for the master branch - push: - branches: [master] - pull_request: - branches: [master] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10'] - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Display Python version - run: python --version - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Run tests - run: tox -v diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml new file mode 100644 index 0000000..a2ed158 --- /dev/null +++ b/.github/workflows/mypy.yaml @@ -0,0 +1,24 @@ +name: Mypy testing + +"on": + push: + pull_request: + +jobs: + mypy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Test Typing + run: just mypy diff --git a/.github/workflows/pip-audit.yaml b/.github/workflows/pip-audit.yaml new file mode 100644 index 0000000..4c9bfb2 --- /dev/null +++ b/.github/workflows/pip-audit.yaml @@ -0,0 +1,23 @@ +name: Pip-Audit + +"on": + push: + pull_request: + +jobs: + selftest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - uses: pypa/gh-action-pip-audit@v1.0.8 diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml new file mode 100644 index 0000000..241e314 --- /dev/null +++ b/.github/workflows/pypi.yaml @@ -0,0 +1,93 @@ +name: Publish Python distribution to PyPI and TestPyPI + +"on": push + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python distribution to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/protocol-implements-decorator/ # Replace with your PyPI project name + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + github-release: + name: >- + Sign the Python distribution with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 0000000..fc12c26 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,27 @@ +name: PyTest + +"on": + push: + pull_request: + +env: + COLUMNS: 120 + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Run Tests + run: just pytest diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml new file mode 100644 index 0000000..72009ea --- /dev/null +++ b/.github/workflows/ruff.yaml @@ -0,0 +1,24 @@ +name: Ruff Linting + +"on": + push: + pull_request: + +jobs: + black: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Test Formatting + run: just ruff_check diff --git a/.github/workflows/tomlsort.yaml b/.github/workflows/tomlsort.yaml new file mode 100644 index 0000000..bf63104 --- /dev/null +++ b/.github/workflows/tomlsort.yaml @@ -0,0 +1,27 @@ +name: TOML Formatting + +"on": + push: + pull_request: + +jobs: + tomlsort: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version-file: .python-version + cache: 'pip' + + - uses: taiki-e/install-action@just + + - name: Install Dependencies + run: just install + + - name: Install toml-sort + run: pip install toml-sort + + - name: Test Typing + run: just tomlsort_check diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml new file mode 100644 index 0000000..c33f979 --- /dev/null +++ b/.github/workflows/tox.yaml @@ -0,0 +1,30 @@ +name: TOX testing + +"on": + push: + pull_request: + +jobs: + tox: + runs-on: ubuntu-latest + env: + TOX_PARALLEL_NO_SPINNER: 1 # Removes logging spam + steps: + - name: Checkout and setup Pythons + uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + - name: Install tox and run tests + run: | + pip install tox + tox --parallel diff --git a/.gitignore b/.gitignore index af4b16f..1b3a25f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,161 @@ +# Byte-compiled / optimized / DLL files __pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ -.vscode/ -.tox/ +develop-eggs/ +dist/ +downloads/ eggs/ .eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ *.egg-info/ -~* -.mypy_cache/ -.pspp -.cache/ -.viminfo -.idea/ -desktop.ini -.gitconfig -.recommenders -.metadata/ -.venv/ +.installed.cfg +*.egg +MANIFEST +*.bak + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +#.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +Pipfile.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ venv/ -__pycache__/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +.vscode + +#cprofiler +*.prof + +#taskfile +task diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6a5152..71c0181 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,18 @@ -# See https://pre-commit.com for more information -# See https://pre-commit.com/hooks.html for more hooks minimum_pre_commit_version: 1.21.0 repos: - repo: meta hooks: - - id: check-hooks-apply +# - id: check-hooks-apply - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.6.0 + hooks: + - id: check-ast + - id: check-json + - id: check-toml + - id: check-yaml + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 hooks: - id: trailing-whitespace types: [file, text] @@ -16,8 +21,9 @@ repos: types: [file, text] exclude_types: [html, javascript] - id: check-case-conflict + - id: mixed-line-ending - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.6.0 hooks: - id: check-merge-conflict name: "Check for merge conflicts" @@ -28,134 +34,71 @@ repos: name: "TOML: check toml syntax" types: [file, toml] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.14.3 + rev: 0.28.2 hooks: - id: check-github-workflows - repo: https://github.com/adrienverge/yamllint.git - rev: v1.26.3 # or higher tag + rev: v1.35.1 # or higher tag hooks: - id: yamllint name: "Yaml: Linting files" args: [--format, parsable, --strict] types: [file, yaml] - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.13 + rev: v1.5.5 hooks: - id: remove-tabs name: "Python: Convert Tabs to 4 spaces" args: ['--whitespaces-count', '4'] # defaults to: 4 types: [file, python] - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - name: "Python: Formating files" - args: [--line-length=88, --preview, --safe] - types: [file, python] - repo: https://github.com/asottile/pyupgrade - rev: v2.32.1 + rev: v3.15.2 hooks: - id: pyupgrade name: "Python: upgrade syntax" - args: [--py37-plus] + args: [--py310-plus] - repo: https://github.com/hadialqattan/pycln - rev: v1.3.2 + rev: v2.4.0 hooks: - id: pycln name: "Python: remove unused imports." - - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + - repo: https://github.com/rbroderi/precommithooks + rev: v1.0.2 hooks: - - id: blacken-docs - name: "Python: Formating code in docstrings" - additional_dependencies: [black==22.3] + - id: python_file_name_check + name: "Python: File name check" + args: ["--ignore-test-files"] + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.2 + hooks: + # Run the linter. + - id: ruff + name: "Python: Ruff" types: [file, python] - - repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort - name: "Python: Sorting imports" + args: [--fix] + fail_fast: true + # Run the formatter. + - id: ruff-format + name: "Python: Ruff format" types: [file, python] - args: - - "--multi-line=3" # makes this compatible with add-trailing-comma - - "--trailing-comma" # makes this compatible with add-trailing-comma - - "--profile" - - "black" + fail_fast: true - repo: local hooks: - - id: python_file_name_check - name: "Python: File name check" - entry: "python ./check_names.py" - language: python - pass_filenames: true - types: [file, python] - verbose: false - - repo: https://github.com/pycqa/flake8 - rev: '4.0.1' # old 4.0.1 seem to freeze? - hooks: - - id: flake8 - name: "Python: Linting files (flake8)" - args: # arguments to configure flake8 - # making isort line length compatible with black - - "--max-line-length=88" - - "--max-complexity=18" - - "--kwargs-max-positional-arguments=4" - # allowing these errors now that in the past we ignored. - # D100 Missing docstring in public module - # D103 Missing docstring in public function - # D104 Missing docstring in public package - # D105 Missing docstring in magic method - # D107 Missing docstring in __init__ - # D200 One-line docstring should fit on one line with quotes - # D205 1 blank line required between summary line and description - # D400 First line should end with a period - # D401 First line should be in imperative mood - # D403 First word of the first line should be properly capitalized - # these are errors that will be ignored by flake8 - # VNE002 variable name 'XXX' should be clarified - # W503 see https://www.flake8rules.com/rules/W503.html no longer best practice - # - "--ignore=VNE002,W503" - # removed cohesion as it was causing issues with enum type classes - # E203 spaces around ':' ignoring per https://github.com/psf/black/issues/315 - # PD005 and PD011 falsely flag on other add or values methods - - "--ignore=W503,E203, PD005, PD011" - # when checking with wemake - "--ignore=W503,E203, PD005, PD011, WPS226, WPS112, WPS204, Q000, WPS421, WPS305, WPS237, WPS529, E800, C812, WPS110, WPS360, WPS323" - additional_dependencies: - - flake8-blind-except - - flake8-assert-msg - - flake8-builtins - - flake8-docstrings - - flake8-implicit-str-concat - - flake8-mock - - flake8-variables-names - - pep8-naming - - flake8-bugbear - - flake8-executable - - flake8-raise - - flake8-pytest - - flake8-use-pathlib - - flake8-string-format - - flake8-colors - - flake8-tuple - - pandas-vet - - flake8-length - - flake8-assertive - - flake8-warnings - - flake8-comprehensions - - flake8-simplify - - flake8-noqa - - flake8-force-keyword-arguments - exclude: "setup[.]py|conf[.]py|__init__[.]py" + - id: pylint + name: "Python: Pylint code with Perflint" + entry: python -m pylint + language: system types: [file, python] + args: [-rn, -sn, --load-plugins=perflint] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.3 + rev: v3.1.0 hooks: - id: add-trailing-comma name: "Python: Add trailing comma" - args: [--py36-plus] types: [file, python] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.950' + rev: 'v1.10.0' hooks: - id: mypy name: "Python: Checking variable types" @@ -167,36 +110,12 @@ repos: - pandas-stubs types: [file, python] - repo: https://github.com/PyCQA/bandit - rev: '1.7.4' + rev: '1.7.8' hooks: - id: bandit name: "Python: Checking for potential security issues (bandit)" args: - "--skip=B404,B506,B607,B603,B701,B101,B602" - - repo: https://github.com/jazzband/pip-tools - rev: 6.6.0 - hooks: - - id: pip-compile - name: "Python: Compile any requirements.in to requirements.txt" - args: [--quiet, --no-allow-unsafe, requirements.in] - files: requirements[.]in - - id: pip-compile - name: "Python: Compile any requirements_dev.in to requirements_dev.txt" - args: [--quiet, --no-allow-unsafe, requirements_dev.in] - files: requirements_dev[.]in - - repo: https://github.com/Lucas-C/pre-commit-hooks-safety - rev: v1.2.4 - hooks: - - id: python-safety-dependencies-check - name: "Python: Checking requirements.txt files for vulnerablitites" - always_run: true - files: requirements.txt - args: [requirements.txt] - - id: python-safety-dependencies-check - name: "Python: Checking requirements_dev.txt files for vulnerablitites" - always_run: true - files: requirements_dev.txt - args: [requirements_dev.txt] - repo: local hooks: - id: remove-en-dashes @@ -206,7 +125,7 @@ repos: types: [file] types_or: [python, powershell, lua, jinja] - repo: https://github.com/sirosen/texthooks - rev: 0.3.1 + rev: 0.6.6 hooks: - id: fix-smartquotes types: [file] @@ -217,17 +136,23 @@ repos: - id: forbid-bidi-controls types: [file] types_or: [python, powershell, lua, jinja] + fail_fast: true + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.13.0 + hooks: + - id: pretty-format-java + args: [--autofix] + - id: pretty-format-golang + args: [--autofix] + - id: pretty-format-ini + args: [--autofix] + - id: pretty-format-rust + args: [--autofix] - repo: local hooks: - - id: pdoc - name: "Generate Documentation" - description: 'Auto generating documentation with PDOC' - entry: pdoc - args: [verbex, -o, docs] - language: python - language_version: python3 - require_serial: true - types: [python] + - id: fixes + name: fixes + entry: just _fixes_no_ruff + language: system pass_filenames: false - additional_dependencies: - - beartype + verbose: true diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..8531a3b --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.2 diff --git a/.yamllint b/.yamllint index afbda05..2ce5d39 100644 --- a/.yamllint +++ b/.yamllint @@ -4,3 +4,6 @@ rules: document-start: disable new-lines: disable line-length: disable + truthy: + level: warning + indentation: disable diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a734e49 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Contributor Code of Conduct + +This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. + +For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage. diff --git a/GPLv3_LICENSE.txt b/GPLv3_LICENSE.txt deleted file mode 100644 index f288702..0000000 --- a/GPLv3_LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/LICENSE.txt b/LICENSE.txt index 4b5ad82..ba81a44 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,18 +1,681 @@ Verbal Expressions Copyright (C) 2022 Richard Broderick -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + This file incorporates work covered by the following copyright and permission notice: diff --git a/MANIFEST.IN b/MANIFEST.IN deleted file mode 100644 index 6ed0c64..0000000 --- a/MANIFEST.IN +++ /dev/null @@ -1,3 +0,0 @@ -include verbex/py.typed -include verbex/LICENSE.TXT -include verbex/GPLv3_LICENSE.txt diff --git a/builddoc.bat b/builddoc.bat deleted file mode 100644 index c5f576f..0000000 --- a/builddoc.bat +++ /dev/null @@ -1,3 +0,0 @@ -pushd "%~dp0" -pdoc verbex/verbex -o docs -pause diff --git a/check_names.py b/check_names.py deleted file mode 100644 index 3525104..0000000 --- a/check_names.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Checks module and package names for pep8 compliance.""" -import argparse -import re -from enum import IntEnum -from pathlib import Path - -try: - from exit_codes.exit_codes import ExitCode -except ImportError: - - class ExitCode(IntEnum): # type: ignore - """Redefine in case ExitCode is not installed.""" - - OS_FILE = 1 - DATA_ERR = 2 - OK = 0 - - -SHORT_NAME_LIMIT = 30 - - -def main() -> int: - """Check the file.""" - parser = argparse.ArgumentParser() - parser.add_argument("files", nargs="+") - args = parser.parse_args() - for file_to_check in args.files: - # verify file exists - file_path = Path(file_to_check) - if not file_path.exists(): - print("ERROR: the file doesn't exist") - return ExitCode.OS_FILE - module_name = file_path.stem - package_name = file_path.parent.name - # check length for module and package name - if len(module_name) > SHORT_NAME_LIMIT: - print(f"ERROR: '{module_name}' is longer than {SHORT_NAME_LIMIT}") - return ExitCode.DATA_ERR - if len(package_name) > SHORT_NAME_LIMIT: - print(f"ERROR: '{package_name}' is longer than {SHORT_NAME_LIMIT}") - return ExitCode.DATA_ERR - # check module name - if not re.fullmatch("[A-Za-z_]+", module_name): - if re.fullmatch("[A-Za-z0-9_]+", module_name): - print( - f"WARNING: '{module_name}' has numbers - allowing but note this is" - " not 'strictly' to pep 8 best practices", - ) - else: - print(f"ERROR: '{module_name}' is not all lowercase with underscores") - return ExitCode.DATA_ERR - # check package if exists - # check package name - if package_name.strip() != "" and not re.fullmatch("[A-Za-z]+", package_name): - if re.fullmatch("[A-Za-z0-9]+", package_name): - print( - f"WARNING: '{package_name}' has numbers - allowing but note" - " this is not 'strictly' to pep 8 best practices", - ) - else: - print( - f"ERROR: '{package_name}' is not all lowercase with no underscores", - ) - return ExitCode.DATA_ERR - return ExitCode.OK - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css new file mode 100644 index 0000000..c3defb4 --- /dev/null +++ b/docs/_static/css/custom.css @@ -0,0 +1,9 @@ +@import 'theme.css'; + +.toctree-l4 { + font-size: 1.2em !important +} + +.toctree-l4>a { + padding: 0em 1.618em 0em 5.663em !important +} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..da968d7 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,32 @@ +# type:ignore # noqa: PGH003, INP001 +"""Configuration file for the Sphinx documentation builder. + +For the full list of built-in configuration values, see the documentation: +https://www.sphinx-doc.org/en/master/usage/configuration.html + +-- Project information ----------------------------------------------------- +https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +""" + +import os +import sys + +from sphinx_pyproject import SphinxConfig + +sys.path.insert(0, os.path.abspath("../src")) # noqa: PTH100 +config = SphinxConfig("../pyproject.toml", globalns=globals()) + + +# def setup(app) -> None: +# """Set up the Sphinx application. + +# Args: +# ---- +# app: The Sphinx application object. + +# Returns: +# ------- +# None + +# """ +# app.add_css_file("source/custom.css") diff --git a/docs/getting-started.rst b/docs/getting-started.rst new file mode 100644 index 0000000..0cc088c --- /dev/null +++ b/docs/getting-started.rst @@ -0,0 +1,6 @@ +Getting started +=============== + +This is where you describe how to get set up on a clean install, including the +commands necessary to get the raw data (using the `sync_data_from_s3` command, +for example), and then how to make the cleaned, final data sets. diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 97569ec..0000000 --- a/docs/index.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2451d8d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +.. StringDataDeque documentation master file, created by + sphinx-quickstart on Sun Mar 24 15:35:24 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Verbex's documentation! +=========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + getting-started + source/modules + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..32bb245 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/search.js b/docs/search.js deleted file mode 100644 index 87f83cf..0000000 --- a/docs/search.js +++ /dev/null @@ -1,46 +0,0 @@ -window.pdocSearch = (function(){ -/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o

\n"}, {"fullname": "verbex.verbex", "modulename": "verbex.verbex", "type": "module", "doc": "

Generate regular expressions from an easier fluent verbal form.

\n"}, {"fullname": "verbex.verbex.P", "modulename": "verbex.verbex", "qualname": "P", "type": "variable", "doc": "

\n", "default_value": " = ~P"}, {"fullname": "verbex.verbex.HasIter", "modulename": "verbex.verbex", "qualname": "HasIter", "type": "class", "doc": "

Workaround for mypy P.args.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasIter.__init__", "modulename": "verbex.verbex", "qualname": "HasIter.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems", "modulename": "verbex.verbex", "qualname": "HasItems", "type": "class", "doc": "

Workaround for mypy P.kwargs.

\n", "bases": "typing.Protocol"}, {"fullname": "verbex.verbex.HasItems.__init__", "modulename": "verbex.verbex", "qualname": "HasItems.__init__", "type": "function", "doc": "

\n", "signature": "(self, *args, **kwargs)", "funcdef": "def"}, {"fullname": "verbex.verbex.HasItems.items", "modulename": "verbex.verbex", "qualname": "HasItems.items", "type": "function", "doc": "

Object has items method.

\n\n

Returns:\n The dict of items.

\n", "signature": "(self) -> tuple[str, typing.Any]", "funcdef": "def"}, {"fullname": "verbex.verbex.EscapedText", "modulename": "verbex.verbex", "qualname": "EscapedText", "type": "class", "doc": "

Text that has been escaped for regex.

\n\n

Arguments:\n str -- Extend the string class.

\n", "bases": "builtins.str"}, {"fullname": "verbex.verbex.EscapedText.__init__", "modulename": "verbex.verbex", "qualname": "EscapedText.__init__", "type": "function", "doc": "

Return a escaped regex string.

\n\n

Arguments:\n value -- the string to escape

\n\n

Returns:\n _description_

\n", "signature": "(cls, value: str)", "funcdef": "def"}, {"fullname": "verbex.verbex.re_escape", "modulename": "verbex.verbex", "qualname": "re_escape", "type": "function", "doc": "

Automatically escape any string parameters as EscapedText.

\n\n

Arguments:\n func -- The function to decorate.

\n\n

Returns:\n The decorated function.

\n", "signature": "(\n func: collections.abc.Callable[~P, ~R]\n) -> collections.abc.Callable[~P, ~R]", "funcdef": "def"}, {"fullname": "verbex.verbex.CharClass", "modulename": "verbex.verbex", "qualname": "CharClass", "type": "class", "doc": "

Enum of character classes in regex.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.CharClass.DIGIT", "modulename": "verbex.verbex", "qualname": "CharClass.DIGIT", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.UPPERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.UPPERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.LOWERCASE_LETTER", "modulename": "verbex.verbex", "qualname": "CharClass.LOWERCASE_LETTER", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.WHITESPACE", "modulename": "verbex.verbex", "qualname": "CharClass.WHITESPACE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClass.TAB", "modulename": "verbex.verbex", "qualname": "CharClass.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar", "modulename": "verbex.verbex", "qualname": "SpecialChar", "type": "class", "doc": "

Enum of special charaters, shorthand.

\n\n

Arguments:\n Enum -- Extends the Enum class.

\n", "bases": "enum.Enum"}, {"fullname": "verbex.verbex.SpecialChar.LINEBREAK", "modulename": "verbex.verbex", "qualname": "SpecialChar.LINEBREAK", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.START_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.START_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.END_OF_LINE", "modulename": "verbex.verbex", "qualname": "SpecialChar.END_OF_LINE", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.SpecialChar.TAB", "modulename": "verbex.verbex", "qualname": "SpecialChar.TAB", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.CharClassOrChars", "modulename": "verbex.verbex", "qualname": "CharClassOrChars", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass]"}, {"fullname": "verbex.verbex.EscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "EscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.VerbexEscapedCharClassOrSpecial", "modulename": "verbex.verbex", "qualname": "VerbexEscapedCharClassOrSpecial", "type": "variable", "doc": "

\n", "annotation": ": TypeAlias", "default_value": " = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]"}, {"fullname": "verbex.verbex.Verbex", "modulename": "verbex.verbex", "qualname": "Verbex", "type": "class", "doc": "

VerbalExpressions class.

\n\n

the following methods do not try to match the original js lib!

\n"}, {"fullname": "verbex.verbex.Verbex.__init__", "modulename": "verbex.verbex", "qualname": "Verbex.__init__", "type": "function", "doc": "

Create a Verbex object; setting any needed flags.

\n\n

Keyword Arguments:\n modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

\n", "signature": "(self, modifiers: re.RegexFlag = )", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.EMPTY_REGEX_FLAG", "modulename": "verbex.verbex", "qualname": "Verbex.EMPTY_REGEX_FLAG", "type": "variable", "doc": "

\n", "default_value": " = "}, {"fullname": "verbex.verbex.Verbex.modifiers", "modulename": "verbex.verbex", "qualname": "Verbex.modifiers", "type": "variable", "doc": "

Return the modifiers for this Verbex object.

\n\n

Returns:\n The modifiers applied to this object.

\n", "annotation": ": re.RegexFlag"}, {"fullname": "verbex.verbex.Verbex.regex", "modulename": "verbex.verbex", "qualname": "Verbex.regex", "type": "function", "doc": "

Get a regular expression object.

\n", "signature": "(self) -> Pattern[str]", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.capture_group", "modulename": "verbex.verbex", "qualname": "Verbex.capture_group", "type": "function", "doc": "

Create a capture group.

\n\n

Name is optional if not specified then the first argument is the text.

\n\n

Keyword Arguments:\n name_or_text -- The name of the group / text to search for (default: {None})\n text -- The text to search for (default: {None})

\n\n

Raises:\n ValueError: If name is specified then text must be as well.

\n\n

Returns:\n Verbex with added capture group.

\n", "signature": "(\n self,\n name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.OR", "modulename": "verbex.verbex", "qualname": "Verbex.OR", "type": "function", "doc": "

or is a python keyword so we use OR instead.

\n\n

Arguments:\n text -- Text to find or a Verbex object.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.zero_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.zero_or_more", "type": "function", "doc": "

Find the text or Verbex object zero or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.one_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.one_or_more", "type": "function", "doc": "

Find the text or Verbex object one or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_times", "type": "function", "doc": "

Find the text or Verbex object n or more times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_times_or_more", "modulename": "verbex.verbex", "qualname": "Verbex.n_times_or_more", "type": "function", "doc": "

Find the text or Verbex object at least n times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.n_to_m_times", "modulename": "verbex.verbex", "qualname": "Verbex.n_to_m_times", "type": "function", "doc": "

Find the text or Verbex object between n and m times.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar],\n n: int,\n m: int\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.maybe", "modulename": "verbex.verbex", "qualname": "Verbex.maybe", "type": "function", "doc": "

Possibly find the text / Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to possibly find.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.find", "modulename": "verbex.verbex", "qualname": "Verbex.find", "type": "function", "doc": "

Find the text or Verbex object.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.then", "modulename": "verbex.verbex", "qualname": "Verbex.then", "type": "function", "doc": "

Synonym for find.

\n\n

Arguments:\n text -- The text / Verbex object to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.followed_by", "type": "function", "doc": "

Match if string is followed by text.

\n\n

Positive lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_followed_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_followed_by", "type": "function", "doc": "

Match if string is not followed by text.

\n\n

Negative lookahead

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Positive lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_preceded_by", "modulename": "verbex.verbex", "qualname": "Verbex.not_preceded_by", "type": "function", "doc": "

Match if string is not preceded by text.

\n\n

Negative Lookbehind

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.any_of", "modulename": "verbex.verbex", "qualname": "Verbex.any_of", "type": "function", "doc": "

Find anything in this group of chars or char class.

\n\n

Arguments:\n text -- The characters to look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.not_any_of", "modulename": "verbex.verbex", "qualname": "Verbex.not_any_of", "type": "function", "doc": "

Find anything but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n text: Union[str, verbex.verbex.CharClass]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything_but", "modulename": "verbex.verbex", "qualname": "Verbex.anything_but", "type": "function", "doc": "

Find anything one or more times but this group of chars or char class.

\n\n

Arguments:\n text -- The characters to not look for.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.start_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.start_of_line", "type": "function", "doc": "

Find the start of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.end_of_line", "modulename": "verbex.verbex", "qualname": "Verbex.end_of_line", "type": "function", "doc": "

Find the end of the line.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.line_break", "modulename": "verbex.verbex", "qualname": "Verbex.line_break", "type": "function", "doc": "

Find a line break.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.tab", "modulename": "verbex.verbex", "qualname": "Verbex.tab", "type": "function", "doc": "

Find a tab.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.anything", "modulename": "verbex.verbex", "qualname": "Verbex.anything", "type": "function", "doc": "

Find anything one or more time.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.as_few", "modulename": "verbex.verbex", "qualname": "Verbex.as_few", "type": "function", "doc": "

Modify previous search to not be greedy.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.number_range", "modulename": "verbex.verbex", "qualname": "Verbex.number_range", "type": "function", "doc": "

Generate a range of numbers.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self, start: int, end: int) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.letter_range", "modulename": "verbex.verbex", "qualname": "Verbex.letter_range", "type": "function", "doc": "

Generate a range of letters.

\n\n

Arguments:\n start -- Start of the range\n end -- End of the range

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(\n self,\n start: typing.Annotated[str, Is[_string_len_is_1]],\n end: typing.Annotated[str, Is[_string_len_is_1]]\n) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.word", "modulename": "verbex.verbex", "qualname": "Verbex.word", "type": "function", "doc": "

Find a word on word boundary.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_any_case", "modulename": "verbex.verbex", "qualname": "Verbex.with_any_case", "type": "function", "doc": "

Modify Verbex object to be case insensitive.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.search_by_line", "modulename": "verbex.verbex", "qualname": "Verbex.search_by_line", "type": "function", "doc": "

Search each line, ^ and $ match begining and end of line respectively.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}, {"fullname": "verbex.verbex.Verbex.with_ascii", "modulename": "verbex.verbex", "qualname": "Verbex.with_ascii", "type": "function", "doc": "

Match ascii instead of unicode.

\n\n

Returns:\n Modified Verbex object.

\n", "signature": "(self) -> verbex.verbex.Verbex", "funcdef": "def"}]; - - // mirrored in build-search-index.js (part 1) - // Also split on html tags. this is a cheap heuristic, but good enough. - elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); - - let searchIndex; - if (docs._isPrebuiltIndex) { - console.info("using precompiled search index"); - searchIndex = elasticlunr.Index.load(docs); - } else { - console.time("building search index"); - // mirrored in build-search-index.js (part 2) - searchIndex = elasticlunr(function () { - this.pipeline.remove(elasticlunr.stemmer); - this.pipeline.remove(elasticlunr.stopWordFilter); - this.addField("qualname"); - this.addField("fullname"); - this.addField("annotation"); - this.addField("default_value"); - this.addField("signature"); - this.addField("bases"); - this.addField("doc"); - this.setRef("fullname"); - }); - for (let doc of docs) { - searchIndex.addDoc(doc); - } - console.timeEnd("building search index"); - } - - return (term) => searchIndex.search(term, { - fields: { - qualname: {boost: 4}, - fullname: {boost: 2}, - annotation: {boost: 2}, - default_value: {boost: 2}, - signature: {boost: 2}, - bases: {boost: 2}, - doc: {boost: 1}, - }, - expand: true - }); -})(); \ No newline at end of file diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..4415b88 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +Verbex +=============== + +.. toctree:: + :maxdepth: 4 + + verbex diff --git a/docs/source/verbex.rst b/docs/source/verbex.rst new file mode 100644 index 0000000..bb97781 --- /dev/null +++ b/docs/source/verbex.rst @@ -0,0 +1,13 @@ +verbex package +======================= + +Submodules +---------- + +verbex module +------------------------------------------- + +.. automodule:: verbex + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/verbex.html b/docs/verbex.html deleted file mode 100644 index c89b400..0000000 --- a/docs/verbex.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - - verbex API documentation - - - - - - - - - -
-
-

-verbex

- - -
- View Source -
0try:
-1    from importlib.metadata import version
-2except ImportError:
-3    from importlib_metadata import version  # type: ignore
-4
-5from .verbex import CharClass as CharClass
-6from .verbex import SpecialChar as SpecialChar
-7from .verbex import Verbex as Verbex
-8
-9__version__ = version("verbex")
-
- -
- -
-
- - \ No newline at end of file diff --git a/docs/verbex/verbex.html b/docs/verbex/verbex.html deleted file mode 100644 index 17bbb44..0000000 --- a/docs/verbex/verbex.html +++ /dev/null @@ -1,3346 +0,0 @@ - - - - - - - verbex.verbex API documentation - - - - - - - - - -
-
-

-verbex.verbex

- -

Generate regular expressions from an easier fluent verbal form.

-
- -
- View Source -
  0"""Generate regular expressions from an easier fluent verbal form."""
-  1from __future__ import annotations
-  2
-  3import re
-  4from enum import Enum
-  5from functools import wraps
-  6
-  7try:
-  8    from typing import (  # <--------------- if Python ≥ 3.9.0
-  9        Annotated,
- 10        ParamSpec,
- 11        Protocol,
- 12        TypeAlias,
- 13        runtime_checkable,
- 14    )
- 15except ImportError:
- 16    from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable  # type: ignore # <--- if Python < 3.9.0 # noqa E501
- 17
- 18from typing import TYPE_CHECKING, Pattern, TypeVar
- 19
- 20if TYPE_CHECKING:
- 21    from typing import Protocol  # noqa: F811
- 22
- 23from beartype import beartype  # type: ignore
- 24from beartype.typing import (  # type: ignore
- 25    Any,
- 26    Callable,
- 27    Dict,
- 28    Iterator,
- 29    List,
- 30    Optional,
- 31    Tuple,
- 32    Union,
- 33    cast,
- 34)
- 35from beartype.vale import Is  # type: ignore
- 36
- 37
- 38def _string_len_is_1(text: object) -> bool:
- 39    return isinstance(text, str) and len(text) == 1
- 40
- 41
- 42Char = Annotated[str, Is[_string_len_is_1]]
- 43
- 44
- 45P = ParamSpec("P")  # noqa: VNE001
- 46R = TypeVar("R")  # noqa: VNE001
- 47
- 48
- 49# work around for bug https://github.com/python/mypy/issues/12660
- 50# fixed in next version of mypy.
- 51@runtime_checkable
- 52class HasIter(Protocol):
- 53    """Workaround for mypy P.args."""
- 54
- 55    def __iter__(self) -> Iterator[Any]:
- 56        """Object can be iterated.
- 57
- 58        Yields:
- 59            Next object.
- 60        """
- 61        ...
- 62
- 63
- 64# work around for bug https://github.com/python/mypy/issues/12660
- 65# fixed in next version of mypy
- 66@runtime_checkable
- 67class HasItems(Protocol):
- 68    """Workaround for mypy P.kwargs."""
- 69
- 70    def items(self) -> Tuple[str, Any]:
- 71        """Object has items method.
- 72
- 73        Returns:
- 74            The dict of items.
- 75        """
- 76        ...
- 77
- 78
- 79class EscapedText(str):
- 80    """Text that has been escaped for regex.
- 81
- 82    Arguments:
- 83        str -- Extend the string class.
- 84    """
- 85
- 86    def __new__(cls, value: str) -> EscapedText:
- 87        """Return a escaped regex string.
- 88
- 89        Arguments:
- 90            value -- the string to escape
- 91
- 92        Returns:
- 93            _description_
- 94        """
- 95        return str.__new__(cls, re.escape(value))
- 96
- 97
- 98def re_escape(func: Callable[P, R]) -> Callable[P, R]:
- 99    """Automatically escape any string parameters as EscapedText.
-100
-101    Arguments:
-102        func -- The function to decorate.
-103
-104    Returns:
-105        The decorated function.
-106    """
-107
-108    @wraps(func)
-109    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-110        escaped_args: List[Any] = []
-111        escaped_kwargs: Dict[str, Any] = {}
-112        for arg in cast(HasIter, args):
-113            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-114                escaped_args.append(EscapedText(arg))
-115            else:
-116                escaped_args.append(arg)
-117        arg_k: str
-118        arg_v: Any
-119        for arg_k, arg_v in cast(HasItems, kwargs).items():
-120            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-121                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-122            else:
-123                escaped_kwargs[arg_k] = arg_v
-124        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-125
-126    return inner
-127
-128
-129class CharClass(Enum):
-130    """Enum of character classes in regex.
-131
-132    Arguments:
-133        Enum -- Extends the Enum class.
-134    """
-135
-136    DIGIT = "\\d"
-137    LETTER = "\\w"
-138    UPPERCASE_LETTER = "\\u"
-139    LOWERCASE_LETTER = "\\l"
-140    WHITESPACE = "\\s"
-141    TAB = "\\t"
-142
-143    def __str__(self) -> str:
-144        """To string method based on Enum value.
-145
-146        Returns:
-147            value of Enum
-148        """
-149        return self.value
-150
-151
-152class SpecialChar(Enum):
-153    """Enum of special charaters, shorthand.
-154
-155    Arguments:
-156        Enum -- Extends the Enum class.
-157    """
-158
-159    # does not work  / should not be used in [ ]
-160    LINEBREAK = "(\\n|(\\r\\n))"
-161    START_OF_LINE = "^"
-162    END_OF_LINE = "$"
-163    TAB = "\t"
-164
-165    def __str__(self) -> str:
-166        """To string for special chars enum.
-167
-168        Returns:
-169            Return value of enum as string.
-170        """
-171        return self.value
-172
-173
-174CharClassOrChars: TypeAlias = Union[str, CharClass]
-175EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar]
-176VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial]
-177
-178
-179def _poseur_decorator(*poseur: Any) -> Any:
-180    """Positional-only arguments runtime checker."""
-181    import functools
-182
-183    def caller(func: Callable[P, R]) -> Callable[P, R]:  # type: ignore
-184        @functools.wraps(func)
-185        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
-186            poseur_args = set(poseur).intersection(kwargs)  # type: ignore
-187            if poseur_args:
-188                raise TypeError(
-189                    "%s() got some positional-only arguments passed as keyword"
-190                    " arguments: %r" % (func.__name__, ", ".join(poseur_args)),
-191                )
-192            return func(*args, **kwargs)  # type: ignore
-193
-194        return wrapper
-195
-196    return caller
-197
-198
-199class Verbex:
-200    """
-201    VerbalExpressions class.
-202
-203    the following methods do not try to match the original js lib!
-204    """
-205
-206    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-207
-208    @re_escape
-209    @beartype
-210    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-211        """Create a Verbex object; setting any needed flags.
-212
-213        Keyword Arguments:
-214            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-215        """
-216        # self._parts: List[str] = [text]
-217        self._parts: List[str] = []
-218        self._modifiers = modifiers
-219
-220    @property
-221    def modifiers(self) -> re.RegexFlag:
-222        """Return the modifiers for this Verbex object.
-223
-224        Returns:
-225            The modifiers applied to this object.
-226        """
-227        return self._modifiers
-228
-229    def __str__(self) -> str:
-230        """Return regex string representation."""
-231        return "".join(self._parts)
-232
-233    @beartype
-234    def _add(self, value: Union[str, List[str]]) -> Verbex:
-235        """
-236        Append a transformed value to internal expression to be compiled.
-237
-238        As possible, this method should be "private".
-239        """
-240        if isinstance(value, list):
-241            self._parts.extend(value)
-242        else:
-243            self._parts.append(value)
-244        return self
-245
-246    def regex(self) -> Pattern[str]:
-247        """Get a regular expression object."""
-248        return re.compile(
-249            str(self),
-250            self._modifiers,
-251        )
-252
-253    # allow VerbexEscapedCharClassOrSpecial
-254
-255    @re_escape
-256    @beartype
-257    def _capture_group_with_name(
-258        self,
-259        name: str,
-260        text: VerbexEscapedCharClassOrSpecial,
-261    ) -> Verbex:
-262        return self._add(f"(?<{name}>{str(text)})")
-263
-264    @re_escape
-265    @beartype
-266    def _capture_group_without_name(
-267        self,
-268        text: VerbexEscapedCharClassOrSpecial,
-269    ) -> Verbex:
-270        return self._add(f"({str(text)})")
-271
-272    @re_escape
-273    @beartype
-274    @_poseur_decorator("self")
-275    def capture_group(
-276        self,
-277        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-278        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-279    ) -> Verbex:
-280        """Create a capture group.
-281
-282        Name is optional if not specified then the first argument is the text.
-283
-284        Keyword Arguments:
-285            name_or_text -- The name of the group / text to search for (default: {None})
-286            text -- The text to search for (default: {None})
-287
-288        Raises:
-289            ValueError: If name is specified then text must be as well.
-290
-291        Returns:
-292            Verbex with added capture group.
-293        """
-294        if name_or_text is not None:
-295            if text is None:
-296                _text = name_or_text
-297                return self._capture_group_without_name(_text)
-298            if isinstance(name_or_text, str):
-299                return self._capture_group_with_name(name_or_text, text)
-300        raise ValueError("text must be specified with optional name")
-301
-302    @re_escape
-303    @beartype
-304    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa: N802
-305        """`or` is a python keyword so we use `OR` instead.
-306
-307        Arguments:
-308            text -- Text to find or a Verbex object.
-309
-310        Returns:
-311            Modified Verbex object.
-312        """
-313        return self._add("|").find(text)
-314
-315    @re_escape
-316    @beartype
-317    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-318        """Find the text or Verbex object zero or more times.
-319
-320        Arguments:
-321            text -- The text / Verbex object to look for.
-322
-323        Returns:
-324            Modified Verbex object.
-325        """
-326        return self._add(f"(?:{str(text)})*")
-327
-328    @re_escape
-329    @beartype
-330    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-331        """Find the text or Verbex object one or more times.
-332
-333        Arguments:
-334            text -- The text / Verbex object to look for.
-335
-336        Returns:
-337            Modified Verbex object.
-338        """
-339        return self._add(f"(?:{str(text)})+")
-340
-341    @re_escape
-342    @beartype
-343    def n_times(
-344        self,
-345        text: VerbexEscapedCharClassOrSpecial,
-346        n: int,  # noqa: VNE001
-347    ) -> Verbex:
-348        """Find the text or Verbex object n or more times.
-349
-350        Arguments:
-351            text -- The text / Verbex object to look for.
-352
-353        Returns:
-354            Modified Verbex object.
-355        """
-356        return self._add(f"(?:{str(text)}){{{n}}}")
-357
-358    @re_escape
-359    @beartype
-360    def n_times_or_more(
-361        self,
-362        text: VerbexEscapedCharClassOrSpecial,
-363        n: int,  # noqa: VNE001
-364    ) -> Verbex:
-365        """Find the text or Verbex object at least n times.
-366
-367        Arguments:
-368            text -- The text / Verbex object to look for.
-369
-370        Returns:
-371            Modified Verbex object.
-372        """
-373        return self._add(f"(?:{str(text)}){{{n},}}")
-374
-375    @re_escape
-376    @beartype
-377    def n_to_m_times(
-378        self,
-379        text: VerbexEscapedCharClassOrSpecial,
-380        n: int,  # noqa: VNE001
-381        m: int,  # noqa: VNE001
-382    ) -> Verbex:
-383        """Find the text or Verbex object between n and m times.
-384
-385        Arguments:
-386            text -- The text / Verbex object to look for.
-387
-388        Returns:
-389            Modified Verbex object.
-390        """
-391        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-392
-393    @re_escape
-394    @beartype
-395    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-396        """Possibly find the text / Verbex object.
-397
-398        Arguments:
-399            text -- The text / Verbex object to possibly find.
-400
-401        Returns:
-402            Modified Verbex object.
-403        """
-404        return self._add(f"(?:{str(text)})?")
-405
-406    @re_escape
-407    @beartype
-408    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-409        """Find the text or Verbex object.
-410
-411        Arguments:
-412            text -- The text / Verbex object to look for.
-413
-414        Returns:
-415            Modified Verbex object.
-416        """
-417        return self._add(str(text))
-418
-419    @re_escape
-420    @beartype
-421    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-422        """Synonym for find.
-423
-424        Arguments:
-425            text -- The text / Verbex object to look for.
-426
-427        Returns:
-428            Modified Verbex object.
-429        """
-430        return self.find(text)
-431
-432    @re_escape
-433    @beartype
-434    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-435        """Match if string is followed by text.
-436
-437        Positive lookahead
-438
-439        Returns:
-440            Modified Verbex object.
-441        """
-442        return self._add(f"(?={text})")
-443
-444    @re_escape
-445    @beartype
-446    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-447        """Match if string is not followed by text.
-448
-449        Negative lookahead
-450
-451        Returns:
-452            Modified Verbex object.
-453        """
-454        return self._add(f"(?!{text})")
-455
-456    @re_escape
-457    @beartype
-458    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-459        """Match if string is not preceded by text.
-460
-461        Positive lookbehind
-462
-463        Returns:
-464            Modified Verbex object.
-465        """
-466        return self._add(f"(?<={text})")
-467
-468    @re_escape
-469    @beartype
-470    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-471        """Match if string is not preceded by text.
-472
-473        Negative Lookbehind
-474
-475        Returns:
-476            Modified Verbex object.
-477        """
-478        return self._add(f"(?<!{text})")
-479
-480    # only allow CharclassOrChars
-481
-482    @re_escape
-483    @beartype
-484    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-485        """Find anything in this group of chars or char class.
-486
-487        Arguments:
-488            text -- The characters to look for.
-489
-490        Returns:
-491            Modified Verbex object.
-492        """
-493        return self._add(f"(?:[{chargroup}])")
-494
-495    @re_escape
-496    @beartype
-497    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-498        """Find anything but this group of chars or char class.
-499
-500        Arguments:
-501            text -- The characters to not look for.
-502
-503        Returns:
-504            Modified Verbex object.
-505        """
-506        return self._add(f"(?:[^{text}])")
-507
-508    @re_escape
-509    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-510        """Find anything one or more times but this group of chars or char class.
-511
-512        Arguments:
-513            text -- The characters to not look for.
-514
-515        Returns:
-516            Modified Verbex object.
-517        """
-518        return self._add(f"[^{chargroup}]+")
-519
-520    # no text input
-521
-522    def start_of_line(self) -> Verbex:
-523        """Find the start of the line.
-524
-525        Returns:
-526            Modified Verbex object.
-527        """
-528        return self.find(SpecialChar.START_OF_LINE)
-529
-530    def end_of_line(self) -> Verbex:
-531        """Find the end of the line.
-532
-533        Returns:
-534            Modified Verbex object.
-535        """
-536        return self.find(SpecialChar.END_OF_LINE)
-537
-538    def line_break(self) -> Verbex:
-539        """Find a line break.
-540
-541        Returns:
-542            Modified Verbex object.
-543        """
-544        return self.find(SpecialChar.LINEBREAK)
-545
-546    def tab(self) -> Verbex:
-547        """Find a tab.
-548
-549        Returns:
-550            Modified Verbex object.
-551        """
-552        return self.find(SpecialChar.TAB)
-553
-554    def anything(self) -> Verbex:
-555        """Find anything one or more time.
-556
-557        Returns:
-558            Modified Verbex object.
-559        """
-560        return self._add(".+")
-561
-562    def as_few(self) -> Verbex:
-563        """Modify previous search to not be greedy.
-564
-565        Returns:
-566            Modified Verbex object.
-567        """
-568        return self._add("?")
-569
-570    @beartype
-571    def number_range(self, start: int, end: int) -> Verbex:
-572        """Generate a range of numbers.
-573
-574        Arguments:
-575            start -- Start of the range
-576            end -- End of the range
-577
-578        Returns:
-579            Modified Verbex object.
-580        """
-581        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-582
-583    @beartype
-584    def letter_range(self, start: Char, end: Char) -> Verbex:
-585        """Generate a range of letters.
-586
-587        Arguments:
-588            start -- Start of the range
-589            end -- End of the range
-590
-591        Returns:
-592            Modified Verbex object.
-593        """
-594        return self._add(f"[{start}-{end}]")
-595
-596    def word(self) -> Verbex:
-597        """Find a word on word boundary.
-598
-599        Returns:
-600            Modified Verbex object.
-601        """
-602        return self._add("(\\b\\w+\\b)")
-603
-604    # # --------------- modifiers ------------------------
-605
-606    def with_any_case(self) -> Verbex:
-607        """Modify Verbex object to be case insensitive.
-608
-609        Returns:
-610            Modified Verbex object.
-611        """
-612        self._modifiers |= re.IGNORECASE
-613        return self
-614
-615    def search_by_line(self) -> Verbex:
-616        """Search each line, ^ and $ match begining and end of line respectively.
-617
-618        Returns:
-619            Modified Verbex object.
-620        """
-621        self._modifiers |= re.MULTILINE
-622        return self
-623
-624    def with_ascii(self) -> Verbex:
-625        """Match ascii instead of unicode.
-626
-627        Returns:
-628            Modified Verbex object.
-629        """
-630        self._modifiers |= re.ASCII
-631        return self
-632
-633
-634# left over notes from original version
-635# def __getattr__(self, attr):
-636#     """ any other function will be sent to the regex object """
-637#     regex = self.regex()
-638#     return getattr(regex, attr)
-639
-640# def replace(self, string, repl):
-641#     return self.sub(repl, string)
-642
-643
-644if __name__ == "__main__":
-645    pass
-
- -
- -
-
-
#   - - P = ~P -
- - - - -
-
-
- #   - -
@runtime_checkable
- - class - HasIter(typing.Protocol): -
- -
- View Source -
52@runtime_checkable
-53class HasIter(Protocol):
-54    """Workaround for mypy P.args."""
-55
-56    def __iter__(self) -> Iterator[Any]:
-57        """Object can be iterated.
-58
-59        Yields:
-60            Next object.
-61        """
-62        ...
-
- -
- -

Workaround for mypy P.args.

-
- - -
-
#   - - - HasIter(*args, **kwargs) -
- -
- View Source -
1429def _no_init_or_replace_init(self, *args, **kwargs):
-1430    cls = type(self)
-1431
-1432    if cls._is_protocol:
-1433        raise TypeError('Protocols cannot be instantiated')
-1434
-1435    # Already using a custom `__init__`. No need to calculate correct
-1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
-1437    if cls.__init__ is not _no_init_or_replace_init:
-1438        return
-1439
-1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
-1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
-1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
-1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
-1444    # instantiation of the protocol subclass will thus use the new
-1445    # `__init__` and no longer call `_no_init_or_replace_init`.
-1446    for base in cls.__mro__:
-1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
-1448        if init is not _no_init_or_replace_init:
-1449            cls.__init__ = init
-1450            break
-1451    else:
-1452        # should not happen
-1453        cls.__init__ = object.__init__
-1454
-1455    cls.__init__(self, *args, **kwargs)
-
- -
- - - -
-
-
-
- #   - -
@runtime_checkable
- - class - HasItems(typing.Protocol): -
- -
- View Source -
67@runtime_checkable
-68class HasItems(Protocol):
-69    """Workaround for mypy P.kwargs."""
-70
-71    def items(self) -> Tuple[str, Any]:
-72        """Object has items method.
-73
-74        Returns:
-75            The dict of items.
-76        """
-77        ...
-
- -
- -

Workaround for mypy P.kwargs.

-
- - -
-
#   - - - HasItems(*args, **kwargs) -
- -
- View Source -
1429def _no_init_or_replace_init(self, *args, **kwargs):
-1430    cls = type(self)
-1431
-1432    if cls._is_protocol:
-1433        raise TypeError('Protocols cannot be instantiated')
-1434
-1435    # Already using a custom `__init__`. No need to calculate correct
-1436    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
-1437    if cls.__init__ is not _no_init_or_replace_init:
-1438        return
-1439
-1440    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
-1441    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
-1442    # searches for a proper new `__init__` in the MRO. The new `__init__`
-1443    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
-1444    # instantiation of the protocol subclass will thus use the new
-1445    # `__init__` and no longer call `_no_init_or_replace_init`.
-1446    for base in cls.__mro__:
-1447        init = base.__dict__.get('__init__', _no_init_or_replace_init)
-1448        if init is not _no_init_or_replace_init:
-1449            cls.__init__ = init
-1450            break
-1451    else:
-1452        # should not happen
-1453        cls.__init__ = object.__init__
-1454
-1455    cls.__init__(self, *args, **kwargs)
-
- -
- - - -
-
-
#   - - - def - items(self) -> tuple[str, typing.Any]: -
- -
- View Source -
71    def items(self) -> Tuple[str, Any]:
-72        """Object has items method.
-73
-74        Returns:
-75            The dict of items.
-76        """
-77        ...
-
- -
- -

Object has items method.

- -

Returns: - The dict of items.

-
- - -
-
-
-
- #   - - - class - EscapedText(builtins.str): -
- -
- View Source -
80class EscapedText(str):
-81    """Text that has been escaped for regex.
-82
-83    Arguments:
-84        str -- Extend the string class.
-85    """
-86
-87    def __new__(cls, value: str) -> EscapedText:
-88        """Return a escaped regex string.
-89
-90        Arguments:
-91            value -- the string to escape
-92
-93        Returns:
-94            _description_
-95        """
-96        return str.__new__(cls, re.escape(value))
-
- -
- -

Text that has been escaped for regex.

- -

Arguments: - str -- Extend the string class.

-
- - -
-
#   - - - EscapedText(value: str) -
- -
- View Source -
87    def __new__(cls, value: str) -> EscapedText:
-88        """Return a escaped regex string.
-89
-90        Arguments:
-91            value -- the string to escape
-92
-93        Returns:
-94            _description_
-95        """
-96        return str.__new__(cls, re.escape(value))
-
- -
- -

Return a escaped regex string.

- -

Arguments: - value -- the string to escape

- -

Returns: - _description_

-
- - -
-
-
Inherited Members
-
-
builtins.str
-
encode
-
replace
-
split
-
rsplit
-
join
-
capitalize
-
casefold
-
title
-
center
-
count
-
expandtabs
-
find
-
partition
-
index
-
ljust
-
lower
-
lstrip
-
rfind
-
rindex
-
rjust
-
rstrip
-
rpartition
-
splitlines
-
strip
-
swapcase
-
translate
-
upper
-
startswith
-
endswith
-
removeprefix
-
removesuffix
-
isascii
-
islower
-
isupper
-
istitle
-
isspace
-
isdecimal
-
isdigit
-
isnumeric
-
isalpha
-
isalnum
-
isidentifier
-
isprintable
-
zfill
-
format
-
format_map
-
maketrans
- -
-
-
-
-
-
#   - - - def - re_escape( - func: collections.abc.Callable[~P, ~R] -) -> collections.abc.Callable[~P, ~R]: -
- -
- View Source -
 99def re_escape(func: Callable[P, R]) -> Callable[P, R]:
-100    """Automatically escape any string parameters as EscapedText.
-101
-102    Arguments:
-103        func -- The function to decorate.
-104
-105    Returns:
-106        The decorated function.
-107    """
-108
-109    @wraps(func)
-110    def inner(*args: P.args, **kwargs: P.kwargs) -> R:  # type: ignore
-111        escaped_args: List[Any] = []
-112        escaped_kwargs: Dict[str, Any] = {}
-113        for arg in cast(HasIter, args):
-114            if not isinstance(arg, EscapedText) and isinstance(arg, str):
-115                escaped_args.append(EscapedText(arg))
-116            else:
-117                escaped_args.append(arg)
-118        arg_k: str
-119        arg_v: Any
-120        for arg_k, arg_v in cast(HasItems, kwargs).items():
-121            if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str):
-122                escaped_kwargs[arg_k] = EscapedText(str(arg_v))
-123            else:
-124                escaped_kwargs[arg_k] = arg_v
-125        return func(*escaped_args, **escaped_kwargs)  # type: ignore
-126
-127    return inner
-
- -
- -

Automatically escape any string parameters as EscapedText.

- -

Arguments: - func -- The function to decorate.

- -

Returns: - The decorated function.

-
- - -
-
-
- #   - - - class - CharClass(enum.Enum): -
- -
- View Source -
130class CharClass(Enum):
-131    """Enum of character classes in regex.
-132
-133    Arguments:
-134        Enum -- Extends the Enum class.
-135    """
-136
-137    DIGIT = "\\d"
-138    LETTER = "\\w"
-139    UPPERCASE_LETTER = "\\u"
-140    LOWERCASE_LETTER = "\\l"
-141    WHITESPACE = "\\s"
-142    TAB = "\\t"
-143
-144    def __str__(self) -> str:
-145        """To string method based on Enum value.
-146
-147        Returns:
-148            value of Enum
-149        """
-150        return self.value
-
- -
- -

Enum of character classes in regex.

- -

Arguments: - Enum -- Extends the Enum class.

-
- - -
-
#   - - DIGIT = <CharClass.DIGIT: '\\d'> -
- - - - -
-
-
#   - - LETTER = <CharClass.LETTER: '\\w'> -
- - - - -
-
-
#   - - UPPERCASE_LETTER = <CharClass.UPPERCASE_LETTER: '\\u'> -
- - - - -
-
-
#   - - LOWERCASE_LETTER = <CharClass.LOWERCASE_LETTER: '\\l'> -
- - - - -
-
-
#   - - WHITESPACE = <CharClass.WHITESPACE: '\\s'> -
- - - - -
-
-
#   - - TAB = <CharClass.TAB: '\\t'> -
- - - - -
-
-
Inherited Members
-
-
enum.Enum
-
name
-
value
- -
-
-
-
-
-
- #   - - - class - SpecialChar(enum.Enum): -
- -
- View Source -
153class SpecialChar(Enum):
-154    """Enum of special charaters, shorthand.
-155
-156    Arguments:
-157        Enum -- Extends the Enum class.
-158    """
-159
-160    # does not work  / should not be used in [ ]
-161    LINEBREAK = "(\\n|(\\r\\n))"
-162    START_OF_LINE = "^"
-163    END_OF_LINE = "$"
-164    TAB = "\t"
-165
-166    def __str__(self) -> str:
-167        """To string for special chars enum.
-168
-169        Returns:
-170            Return value of enum as string.
-171        """
-172        return self.value
-
- -
- -

Enum of special charaters, shorthand.

- -

Arguments: - Enum -- Extends the Enum class.

-
- - -
-
#   - - LINEBREAK = <SpecialChar.LINEBREAK: '(\\n|(\\r\\n))'> -
- - - - -
-
-
#   - - START_OF_LINE = <SpecialChar.START_OF_LINE: '^'> -
- - - - -
-
-
#   - - END_OF_LINE = <SpecialChar.END_OF_LINE: '$'> -
- - - - -
-
-
#   - - TAB = <SpecialChar.TAB: '\t'> -
- - - - -
-
-
Inherited Members
-
-
enum.Enum
-
name
-
value
- -
-
-
-
-
-
#   - - CharClassOrChars: TypeAlias = typing.Union[str, verbex.verbex.CharClass] -
- - - - -
-
-
#   - - EscapedCharClassOrSpecial: TypeAlias = typing.Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -
- - - - -
-
-
#   - - VerbexEscapedCharClassOrSpecial: TypeAlias = typing.Union[ForwardRef('Verbex'), str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -
- - - - -
-
-
- #   - - - class - Verbex: -
- -
- View Source -
200class Verbex:
-201    """
-202    VerbalExpressions class.
-203
-204    the following methods do not try to match the original js lib!
-205    """
-206
-207    EMPTY_REGEX_FLAG = re.RegexFlag(0)
-208
-209    @re_escape
-210    @beartype
-211    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-212        """Create a Verbex object; setting any needed flags.
-213
-214        Keyword Arguments:
-215            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-216        """
-217        # self._parts: List[str] = [text]
-218        self._parts: List[str] = []
-219        self._modifiers = modifiers
-220
-221    @property
-222    def modifiers(self) -> re.RegexFlag:
-223        """Return the modifiers for this Verbex object.
-224
-225        Returns:
-226            The modifiers applied to this object.
-227        """
-228        return self._modifiers
-229
-230    def __str__(self) -> str:
-231        """Return regex string representation."""
-232        return "".join(self._parts)
-233
-234    @beartype
-235    def _add(self, value: Union[str, List[str]]) -> Verbex:
-236        """
-237        Append a transformed value to internal expression to be compiled.
-238
-239        As possible, this method should be "private".
-240        """
-241        if isinstance(value, list):
-242            self._parts.extend(value)
-243        else:
-244            self._parts.append(value)
-245        return self
-246
-247    def regex(self) -> Pattern[str]:
-248        """Get a regular expression object."""
-249        return re.compile(
-250            str(self),
-251            self._modifiers,
-252        )
-253
-254    # allow VerbexEscapedCharClassOrSpecial
-255
-256    @re_escape
-257    @beartype
-258    def _capture_group_with_name(
-259        self,
-260        name: str,
-261        text: VerbexEscapedCharClassOrSpecial,
-262    ) -> Verbex:
-263        return self._add(f"(?<{name}>{str(text)})")
-264
-265    @re_escape
-266    @beartype
-267    def _capture_group_without_name(
-268        self,
-269        text: VerbexEscapedCharClassOrSpecial,
-270    ) -> Verbex:
-271        return self._add(f"({str(text)})")
-272
-273    @re_escape
-274    @beartype
-275    @_poseur_decorator("self")
-276    def capture_group(
-277        self,
-278        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-279        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-280    ) -> Verbex:
-281        """Create a capture group.
-282
-283        Name is optional if not specified then the first argument is the text.
-284
-285        Keyword Arguments:
-286            name_or_text -- The name of the group / text to search for (default: {None})
-287            text -- The text to search for (default: {None})
-288
-289        Raises:
-290            ValueError: If name is specified then text must be as well.
-291
-292        Returns:
-293            Verbex with added capture group.
-294        """
-295        if name_or_text is not None:
-296            if text is None:
-297                _text = name_or_text
-298                return self._capture_group_without_name(_text)
-299            if isinstance(name_or_text, str):
-300                return self._capture_group_with_name(name_or_text, text)
-301        raise ValueError("text must be specified with optional name")
-302
-303    @re_escape
-304    @beartype
-305    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa: N802
-306        """`or` is a python keyword so we use `OR` instead.
-307
-308        Arguments:
-309            text -- Text to find or a Verbex object.
-310
-311        Returns:
-312            Modified Verbex object.
-313        """
-314        return self._add("|").find(text)
-315
-316    @re_escape
-317    @beartype
-318    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-319        """Find the text or Verbex object zero or more times.
-320
-321        Arguments:
-322            text -- The text / Verbex object to look for.
-323
-324        Returns:
-325            Modified Verbex object.
-326        """
-327        return self._add(f"(?:{str(text)})*")
-328
-329    @re_escape
-330    @beartype
-331    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-332        """Find the text or Verbex object one or more times.
-333
-334        Arguments:
-335            text -- The text / Verbex object to look for.
-336
-337        Returns:
-338            Modified Verbex object.
-339        """
-340        return self._add(f"(?:{str(text)})+")
-341
-342    @re_escape
-343    @beartype
-344    def n_times(
-345        self,
-346        text: VerbexEscapedCharClassOrSpecial,
-347        n: int,  # noqa: VNE001
-348    ) -> Verbex:
-349        """Find the text or Verbex object n or more times.
-350
-351        Arguments:
-352            text -- The text / Verbex object to look for.
-353
-354        Returns:
-355            Modified Verbex object.
-356        """
-357        return self._add(f"(?:{str(text)}){{{n}}}")
-358
-359    @re_escape
-360    @beartype
-361    def n_times_or_more(
-362        self,
-363        text: VerbexEscapedCharClassOrSpecial,
-364        n: int,  # noqa: VNE001
-365    ) -> Verbex:
-366        """Find the text or Verbex object at least n times.
-367
-368        Arguments:
-369            text -- The text / Verbex object to look for.
-370
-371        Returns:
-372            Modified Verbex object.
-373        """
-374        return self._add(f"(?:{str(text)}){{{n},}}")
-375
-376    @re_escape
-377    @beartype
-378    def n_to_m_times(
-379        self,
-380        text: VerbexEscapedCharClassOrSpecial,
-381        n: int,  # noqa: VNE001
-382        m: int,  # noqa: VNE001
-383    ) -> Verbex:
-384        """Find the text or Verbex object between n and m times.
-385
-386        Arguments:
-387            text -- The text / Verbex object to look for.
-388
-389        Returns:
-390            Modified Verbex object.
-391        """
-392        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-393
-394    @re_escape
-395    @beartype
-396    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-397        """Possibly find the text / Verbex object.
-398
-399        Arguments:
-400            text -- The text / Verbex object to possibly find.
-401
-402        Returns:
-403            Modified Verbex object.
-404        """
-405        return self._add(f"(?:{str(text)})?")
-406
-407    @re_escape
-408    @beartype
-409    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-410        """Find the text or Verbex object.
-411
-412        Arguments:
-413            text -- The text / Verbex object to look for.
-414
-415        Returns:
-416            Modified Verbex object.
-417        """
-418        return self._add(str(text))
-419
-420    @re_escape
-421    @beartype
-422    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-423        """Synonym for find.
-424
-425        Arguments:
-426            text -- The text / Verbex object to look for.
-427
-428        Returns:
-429            Modified Verbex object.
-430        """
-431        return self.find(text)
-432
-433    @re_escape
-434    @beartype
-435    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-436        """Match if string is followed by text.
-437
-438        Positive lookahead
-439
-440        Returns:
-441            Modified Verbex object.
-442        """
-443        return self._add(f"(?={text})")
-444
-445    @re_escape
-446    @beartype
-447    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-448        """Match if string is not followed by text.
-449
-450        Negative lookahead
-451
-452        Returns:
-453            Modified Verbex object.
-454        """
-455        return self._add(f"(?!{text})")
-456
-457    @re_escape
-458    @beartype
-459    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-460        """Match if string is not preceded by text.
-461
-462        Positive lookbehind
-463
-464        Returns:
-465            Modified Verbex object.
-466        """
-467        return self._add(f"(?<={text})")
-468
-469    @re_escape
-470    @beartype
-471    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-472        """Match if string is not preceded by text.
-473
-474        Negative Lookbehind
-475
-476        Returns:
-477            Modified Verbex object.
-478        """
-479        return self._add(f"(?<!{text})")
-480
-481    # only allow CharclassOrChars
-482
-483    @re_escape
-484    @beartype
-485    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-486        """Find anything in this group of chars or char class.
-487
-488        Arguments:
-489            text -- The characters to look for.
-490
-491        Returns:
-492            Modified Verbex object.
-493        """
-494        return self._add(f"(?:[{chargroup}])")
-495
-496    @re_escape
-497    @beartype
-498    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-499        """Find anything but this group of chars or char class.
-500
-501        Arguments:
-502            text -- The characters to not look for.
-503
-504        Returns:
-505            Modified Verbex object.
-506        """
-507        return self._add(f"(?:[^{text}])")
-508
-509    @re_escape
-510    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-511        """Find anything one or more times but this group of chars or char class.
-512
-513        Arguments:
-514            text -- The characters to not look for.
-515
-516        Returns:
-517            Modified Verbex object.
-518        """
-519        return self._add(f"[^{chargroup}]+")
-520
-521    # no text input
-522
-523    def start_of_line(self) -> Verbex:
-524        """Find the start of the line.
-525
-526        Returns:
-527            Modified Verbex object.
-528        """
-529        return self.find(SpecialChar.START_OF_LINE)
-530
-531    def end_of_line(self) -> Verbex:
-532        """Find the end of the line.
-533
-534        Returns:
-535            Modified Verbex object.
-536        """
-537        return self.find(SpecialChar.END_OF_LINE)
-538
-539    def line_break(self) -> Verbex:
-540        """Find a line break.
-541
-542        Returns:
-543            Modified Verbex object.
-544        """
-545        return self.find(SpecialChar.LINEBREAK)
-546
-547    def tab(self) -> Verbex:
-548        """Find a tab.
-549
-550        Returns:
-551            Modified Verbex object.
-552        """
-553        return self.find(SpecialChar.TAB)
-554
-555    def anything(self) -> Verbex:
-556        """Find anything one or more time.
-557
-558        Returns:
-559            Modified Verbex object.
-560        """
-561        return self._add(".+")
-562
-563    def as_few(self) -> Verbex:
-564        """Modify previous search to not be greedy.
-565
-566        Returns:
-567            Modified Verbex object.
-568        """
-569        return self._add("?")
-570
-571    @beartype
-572    def number_range(self, start: int, end: int) -> Verbex:
-573        """Generate a range of numbers.
-574
-575        Arguments:
-576            start -- Start of the range
-577            end -- End of the range
-578
-579        Returns:
-580            Modified Verbex object.
-581        """
-582        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-583
-584    @beartype
-585    def letter_range(self, start: Char, end: Char) -> Verbex:
-586        """Generate a range of letters.
-587
-588        Arguments:
-589            start -- Start of the range
-590            end -- End of the range
-591
-592        Returns:
-593            Modified Verbex object.
-594        """
-595        return self._add(f"[{start}-{end}]")
-596
-597    def word(self) -> Verbex:
-598        """Find a word on word boundary.
-599
-600        Returns:
-601            Modified Verbex object.
-602        """
-603        return self._add("(\\b\\w+\\b)")
-604
-605    # # --------------- modifiers ------------------------
-606
-607    def with_any_case(self) -> Verbex:
-608        """Modify Verbex object to be case insensitive.
-609
-610        Returns:
-611            Modified Verbex object.
-612        """
-613        self._modifiers |= re.IGNORECASE
-614        return self
-615
-616    def search_by_line(self) -> Verbex:
-617        """Search each line, ^ and $ match begining and end of line respectively.
-618
-619        Returns:
-620            Modified Verbex object.
-621        """
-622        self._modifiers |= re.MULTILINE
-623        return self
-624
-625    def with_ascii(self) -> Verbex:
-626        """Match ascii instead of unicode.
-627
-628        Returns:
-629            Modified Verbex object.
-630        """
-631        self._modifiers |= re.ASCII
-632        return self
-
- -
- -

VerbalExpressions class.

- -

the following methods do not try to match the original js lib!

-
- - -
-
#   - -
@re_escape
-
@beartype
- - Verbex(modifiers: re.RegexFlag = ) -
- -
- View Source -
209    @re_escape
-210    @beartype
-211    def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG):
-212        """Create a Verbex object; setting any needed flags.
-213
-214        Keyword Arguments:
-215            modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})
-216        """
-217        # self._parts: List[str] = [text]
-218        self._parts: List[str] = []
-219        self._modifiers = modifiers
-
- -
- -

Create a Verbex object; setting any needed flags.

- -

Keyword Arguments: - modifiers -- Regex modifying flags (default: {re.RegexFlag(0)})

-
- - -
-
-
#   - - EMPTY_REGEX_FLAG = -
- - - - -
-
-
#   - - modifiers: re.RegexFlag -
- - -

Return the modifiers for this Verbex object.

- -

Returns: - The modifiers applied to this object.

-
- - -
-
-
#   - - - def - regex(self) -> Pattern[str]: -
- -
- View Source -
247    def regex(self) -> Pattern[str]:
-248        """Get a regular expression object."""
-249        return re.compile(
-250            str(self),
-251            self._modifiers,
-252        )
-
- -
- -

Get a regular expression object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - capture_group( - self, - name_or_text: Union[str, NoneType, verbex.verbex.Verbex, verbex.verbex.CharClass, verbex.verbex.SpecialChar] = None, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar, NoneType] = None -) -> verbex.verbex.Verbex: -
- -
- View Source -
273    @re_escape
-274    @beartype
-275    @_poseur_decorator("self")
-276    def capture_group(
-277        self,
-278        name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None,
-279        text: Optional[VerbexEscapedCharClassOrSpecial] = None,
-280    ) -> Verbex:
-281        """Create a capture group.
-282
-283        Name is optional if not specified then the first argument is the text.
-284
-285        Keyword Arguments:
-286            name_or_text -- The name of the group / text to search for (default: {None})
-287            text -- The text to search for (default: {None})
-288
-289        Raises:
-290            ValueError: If name is specified then text must be as well.
-291
-292        Returns:
-293            Verbex with added capture group.
-294        """
-295        if name_or_text is not None:
-296            if text is None:
-297                _text = name_or_text
-298                return self._capture_group_without_name(_text)
-299            if isinstance(name_or_text, str):
-300                return self._capture_group_with_name(name_or_text, text)
-301        raise ValueError("text must be specified with optional name")
-
- -
- -

Create a capture group.

- -

Name is optional if not specified then the first argument is the text.

- -

Keyword Arguments: - name_or_text -- The name of the group / text to search for (default: {None}) - text -- The text to search for (default: {None})

- -

Raises: - ValueError: If name is specified then text must be as well.

- -

Returns: - Verbex with added capture group.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - OR( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
303    @re_escape
-304    @beartype
-305    def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:  # noqa: N802
-306        """`or` is a python keyword so we use `OR` instead.
-307
-308        Arguments:
-309            text -- Text to find or a Verbex object.
-310
-311        Returns:
-312            Modified Verbex object.
-313        """
-314        return self._add("|").find(text)
-
- -
- -

or is a python keyword so we use OR instead.

- -

Arguments: - text -- Text to find or a Verbex object.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - zero_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
316    @re_escape
-317    @beartype
-318    def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-319        """Find the text or Verbex object zero or more times.
-320
-321        Arguments:
-322            text -- The text / Verbex object to look for.
-323
-324        Returns:
-325            Modified Verbex object.
-326        """
-327        return self._add(f"(?:{str(text)})*")
-
- -
- -

Find the text or Verbex object zero or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - one_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
329    @re_escape
-330    @beartype
-331    def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-332        """Find the text or Verbex object one or more times.
-333
-334        Arguments:
-335            text -- The text / Verbex object to look for.
-336
-337        Returns:
-338            Modified Verbex object.
-339        """
-340        return self._add(f"(?:{str(text)})+")
-
- -
- -

Find the text or Verbex object one or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_times( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
342    @re_escape
-343    @beartype
-344    def n_times(
-345        self,
-346        text: VerbexEscapedCharClassOrSpecial,
-347        n: int,  # noqa: VNE001
-348    ) -> Verbex:
-349        """Find the text or Verbex object n or more times.
-350
-351        Arguments:
-352            text -- The text / Verbex object to look for.
-353
-354        Returns:
-355            Modified Verbex object.
-356        """
-357        return self._add(f"(?:{str(text)}){{{n}}}")
-
- -
- -

Find the text or Verbex object n or more times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_times_or_more( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
359    @re_escape
-360    @beartype
-361    def n_times_or_more(
-362        self,
-363        text: VerbexEscapedCharClassOrSpecial,
-364        n: int,  # noqa: VNE001
-365    ) -> Verbex:
-366        """Find the text or Verbex object at least n times.
-367
-368        Arguments:
-369            text -- The text / Verbex object to look for.
-370
-371        Returns:
-372            Modified Verbex object.
-373        """
-374        return self._add(f"(?:{str(text)}){{{n},}}")
-
- -
- -

Find the text or Verbex object at least n times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - n_to_m_times( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar], - n: int, - m: int -) -> verbex.verbex.Verbex: -
- -
- View Source -
376    @re_escape
-377    @beartype
-378    def n_to_m_times(
-379        self,
-380        text: VerbexEscapedCharClassOrSpecial,
-381        n: int,  # noqa: VNE001
-382        m: int,  # noqa: VNE001
-383    ) -> Verbex:
-384        """Find the text or Verbex object between n and m times.
-385
-386        Arguments:
-387            text -- The text / Verbex object to look for.
-388
-389        Returns:
-390            Modified Verbex object.
-391        """
-392        return self._add(f"(?:{str(text)}){{{n},{m}}}")
-
- -
- -

Find the text or Verbex object between n and m times.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - maybe( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
394    @re_escape
-395    @beartype
-396    def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-397        """Possibly find the text / Verbex object.
-398
-399        Arguments:
-400            text -- The text / Verbex object to possibly find.
-401
-402        Returns:
-403            Modified Verbex object.
-404        """
-405        return self._add(f"(?:{str(text)})?")
-
- -
- -

Possibly find the text / Verbex object.

- -

Arguments: - text -- The text / Verbex object to possibly find.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - find( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
407    @re_escape
-408    @beartype
-409    def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-410        """Find the text or Verbex object.
-411
-412        Arguments:
-413            text -- The text / Verbex object to look for.
-414
-415        Returns:
-416            Modified Verbex object.
-417        """
-418        return self._add(str(text))
-
- -
- -

Find the text or Verbex object.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - then( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
420    @re_escape
-421    @beartype
-422    def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-423        """Synonym for find.
-424
-425        Arguments:
-426            text -- The text / Verbex object to look for.
-427
-428        Returns:
-429            Modified Verbex object.
-430        """
-431        return self.find(text)
-
- -
- -

Synonym for find.

- -

Arguments: - text -- The text / Verbex object to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - followed_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
433    @re_escape
-434    @beartype
-435    def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-436        """Match if string is followed by text.
-437
-438        Positive lookahead
-439
-440        Returns:
-441            Modified Verbex object.
-442        """
-443        return self._add(f"(?={text})")
-
- -
- -

Match if string is followed by text.

- -

Positive lookahead

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_followed_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
445    @re_escape
-446    @beartype
-447    def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-448        """Match if string is not followed by text.
-449
-450        Negative lookahead
-451
-452        Returns:
-453            Modified Verbex object.
-454        """
-455        return self._add(f"(?!{text})")
-
- -
- -

Match if string is not followed by text.

- -

Negative lookahead

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - preceded_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
457    @re_escape
-458    @beartype
-459    def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-460        """Match if string is not preceded by text.
-461
-462        Positive lookbehind
-463
-464        Returns:
-465            Modified Verbex object.
-466        """
-467        return self._add(f"(?<={text})")
-
- -
- -

Match if string is not preceded by text.

- -

Positive lookbehind

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_preceded_by( - self, - text: Union[verbex.verbex.Verbex, str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
469    @re_escape
-470    @beartype
-471    def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex:
-472        """Match if string is not preceded by text.
-473
-474        Negative Lookbehind
-475
-476        Returns:
-477            Modified Verbex object.
-478        """
-479        return self._add(f"(?<!{text})")
-
- -
- -

Match if string is not preceded by text.

- -

Negative Lookbehind

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - any_of( - self, - chargroup: Union[str, verbex.verbex.CharClass] -) -> verbex.verbex.Verbex: -
- -
- View Source -
483    @re_escape
-484    @beartype
-485    def any_of(self, chargroup: CharClassOrChars) -> Verbex:
-486        """Find anything in this group of chars or char class.
-487
-488        Arguments:
-489            text -- The characters to look for.
-490
-491        Returns:
-492            Modified Verbex object.
-493        """
-494        return self._add(f"(?:[{chargroup}])")
-
- -
- -

Find anything in this group of chars or char class.

- -

Arguments: - text -- The characters to look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
-
@beartype
- - def - not_any_of( - self, - text: Union[str, verbex.verbex.CharClass] -) -> verbex.verbex.Verbex: -
- -
- View Source -
496    @re_escape
-497    @beartype
-498    def not_any_of(self, text: CharClassOrChars) -> Verbex:
-499        """Find anything but this group of chars or char class.
-500
-501        Arguments:
-502            text -- The characters to not look for.
-503
-504        Returns:
-505            Modified Verbex object.
-506        """
-507        return self._add(f"(?:[^{text}])")
-
- -
- -

Find anything but this group of chars or char class.

- -

Arguments: - text -- The characters to not look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@re_escape
- - def - anything_but( - self, - chargroup: Union[str, verbex.verbex.CharClass, verbex.verbex.SpecialChar] -) -> verbex.verbex.Verbex: -
- -
- View Source -
509    @re_escape
-510    def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex:
-511        """Find anything one or more times but this group of chars or char class.
-512
-513        Arguments:
-514            text -- The characters to not look for.
-515
-516        Returns:
-517            Modified Verbex object.
-518        """
-519        return self._add(f"[^{chargroup}]+")
-
- -
- -

Find anything one or more times but this group of chars or char class.

- -

Arguments: - text -- The characters to not look for.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - start_of_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
523    def start_of_line(self) -> Verbex:
-524        """Find the start of the line.
-525
-526        Returns:
-527            Modified Verbex object.
-528        """
-529        return self.find(SpecialChar.START_OF_LINE)
-
- -
- -

Find the start of the line.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - end_of_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
531    def end_of_line(self) -> Verbex:
-532        """Find the end of the line.
-533
-534        Returns:
-535            Modified Verbex object.
-536        """
-537        return self.find(SpecialChar.END_OF_LINE)
-
- -
- -

Find the end of the line.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - line_break(self) -> verbex.verbex.Verbex: -
- -
- View Source -
539    def line_break(self) -> Verbex:
-540        """Find a line break.
-541
-542        Returns:
-543            Modified Verbex object.
-544        """
-545        return self.find(SpecialChar.LINEBREAK)
-
- -
- -

Find a line break.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - tab(self) -> verbex.verbex.Verbex: -
- -
- View Source -
547    def tab(self) -> Verbex:
-548        """Find a tab.
-549
-550        Returns:
-551            Modified Verbex object.
-552        """
-553        return self.find(SpecialChar.TAB)
-
- -
- -

Find a tab.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - anything(self) -> verbex.verbex.Verbex: -
- -
- View Source -
555    def anything(self) -> Verbex:
-556        """Find anything one or more time.
-557
-558        Returns:
-559            Modified Verbex object.
-560        """
-561        return self._add(".+")
-
- -
- -

Find anything one or more time.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - as_few(self) -> verbex.verbex.Verbex: -
- -
- View Source -
563    def as_few(self) -> Verbex:
-564        """Modify previous search to not be greedy.
-565
-566        Returns:
-567            Modified Verbex object.
-568        """
-569        return self._add("?")
-
- -
- -

Modify previous search to not be greedy.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@beartype
- - def - number_range(self, start: int, end: int) -> verbex.verbex.Verbex: -
- -
- View Source -
571    @beartype
-572    def number_range(self, start: int, end: int) -> Verbex:
-573        """Generate a range of numbers.
-574
-575        Arguments:
-576            start -- Start of the range
-577            end -- End of the range
-578
-579        Returns:
-580            Modified Verbex object.
-581        """
-582        return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")")
-
- -
- -

Generate a range of numbers.

- -

Arguments: - start -- Start of the range - end -- End of the range

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - -
@beartype
- - def - letter_range( - self, - start: typing.Annotated[str, Is[_string_len_is_1]], - end: typing.Annotated[str, Is[_string_len_is_1]] -) -> verbex.verbex.Verbex: -
- -
- View Source -
584    @beartype
-585    def letter_range(self, start: Char, end: Char) -> Verbex:
-586        """Generate a range of letters.
-587
-588        Arguments:
-589            start -- Start of the range
-590            end -- End of the range
-591
-592        Returns:
-593            Modified Verbex object.
-594        """
-595        return self._add(f"[{start}-{end}]")
-
- -
- -

Generate a range of letters.

- -

Arguments: - start -- Start of the range - end -- End of the range

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - word(self) -> verbex.verbex.Verbex: -
- -
- View Source -
597    def word(self) -> Verbex:
-598        """Find a word on word boundary.
-599
-600        Returns:
-601            Modified Verbex object.
-602        """
-603        return self._add("(\\b\\w+\\b)")
-
- -
- -

Find a word on word boundary.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - with_any_case(self) -> verbex.verbex.Verbex: -
- -
- View Source -
607    def with_any_case(self) -> Verbex:
-608        """Modify Verbex object to be case insensitive.
-609
-610        Returns:
-611            Modified Verbex object.
-612        """
-613        self._modifiers |= re.IGNORECASE
-614        return self
-
- -
- -

Modify Verbex object to be case insensitive.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - search_by_line(self) -> verbex.verbex.Verbex: -
- -
- View Source -
616    def search_by_line(self) -> Verbex:
-617        """Search each line, ^ and $ match begining and end of line respectively.
-618
-619        Returns:
-620            Modified Verbex object.
-621        """
-622        self._modifiers |= re.MULTILINE
-623        return self
-
- -
- -

Search each line, ^ and $ match begining and end of line respectively.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
#   - - - def - with_ascii(self) -> verbex.verbex.Verbex: -
- -
- View Source -
625    def with_ascii(self) -> Verbex:
-626        """Match ascii instead of unicode.
-627
-628        Returns:
-629            Modified Verbex object.
-630        """
-631        self._modifiers |= re.ASCII
-632        return self
-
- -
- -

Match ascii instead of unicode.

- -

Returns: - Modified Verbex object.

-
- - -
-
-
- - \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..7856ca7 --- /dev/null +++ b/justfile @@ -0,0 +1,143 @@ +set ignore-comments +PACKAGE_SLUG := "src/precommithooks" +export PYTHON_VERSION := if env("CI","false") != "false" { `python --version|cut -d" " -f2` } else { `cat .python-version` } +PYTHON := if env("USE_SYSTEM_PYTHON", "false") != "false" { "python" } else { ".venv/bin/python" } +PYTHON_ENV := if env("USE_SYSTEM_PYTHON", "false") != "false" { "" } else { "sh .venv/bin/activate &&" } +NEWLINE:="$'\n'" +# print list of commands +help: + @just --list --unsorted +# install into the venv +install: + @# $(PYTHON_PYENV) + {{if env("CI","false") != "false" { "" } else { "pyenv install --skip-existing $PYTHON_VERSION "} }} + @# $(PYTHON_VENV) + {{ if env("USE_SYSTEM_PYTHON", "false") != "false" { "" } else { "python -m venv .venv" } }} + @# pip + {{PYTHON}} -m pip install -e .[dev,optional,docs] + +# Install pre-commit +pre-commit_install: + pre-commit install + +# Setup sphynx autodoc +setup_autodoc: + sphinx-apidoc -f -o docs/source {{PACKAGE_SLUG}} + +# copy as template +copy_as_template DEST: + rsync -r --exclude .mypy_cache --exclude .pytest_cache --exclude .ruff_cache --exclude .tox --exclude .venv --exclude *.egg* --exclude .git ./ {{DEST}} + cd {{DEST}} && git init . && git commit --allow-empty -m 'Make initial root commit' + +# profiling +profile: + python -m cProfile -s time -o timing.prof tests/timing.py --profile + snakeviz timing.prof + +# +# Formatting +# +# Run all linting and fixes +fixes: validate_pyproject ruff_fixes ruff_format_fixes pylint dapperdata_fixes tomlsort_fixes docs pytest + +_fixes_no_ruff: validate_pyproject dapperdata_fixes tomlsort_fixes docs pytest update_dependencies_quiet + +# Validate pyproject.toml format +validate_pyproject: + {{PYTHON}} -m validate_pyproject pyproject.toml + +# Run pylint +pylint: + {{PYTHON}} -m pylint {{PACKAGE_SLUG}} + +# Run Ruff and fix +ruff_fixes: + {{PYTHON}} -m ruff check . --fix + +alias black_check := ruff_format_fixes +#Run Ruff format fixes +ruff_format_fixes: + {{PYTHON}} -m ruff format . + +# Run dapperdata fixes +dapperdata_fixes: + {{PYTHON}} -m dapperdata.cli pretty . --no-dry-run + +# Run Tomlsort fixes +tomlsort_fixes: + {{PYTHON_ENV}} toml-sort `find . -not -path "./.venv/*" -not -path "./.tox/*" -name "*.toml"` -i + +# Generate Docs +docs: + make -C ./docs clean html + +# +# Testing +# +# Run all tests +tests: install pytest ruff_check ruff_format_check mypy dapperdata_check tomlsort_check + +# Run Pytest +pytest: + {{PYTHON}} -m pytest --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + +# Run Pytest verbose +pytestvv: + {{PYTHON}} -m pytest -vv --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + +# Run pytest show strings +pytest_loud: + {{PYTHON}} -m pytest -vv -rA --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + +# Run ruff in check mode +ruff_check: + {{PYTHON}} -m ruff check + +# Run ruff format in check mode +ruff_format_check: + {{PYTHON}} -m ruff format . --check + +# Run mypy check +mypy: + {{PYTHON}} -m mypy {{PACKAGE_SLUG}} + +# Run dapperdata check +dapperdata_check: + {{PYTHON}} -m dapperdata.cli pretty . + +# Run tomlsort_check +tomlsort_check: + {{PYTHON_ENV}} toml-sort `find . -not -path "./.venv/*" -not -path "./.tox/*" -name "*.toml"` --check + +# +# Dependencies +# + +# Rebuild dependencies +rebuild_dependencies: + {{PYTHON}} -m uv pip compile --output-file=requirements.txt pyproject.toml + {{PYTHON}} -m uv pip compile --output-file=requirements-dev.txt --extra=dev pyproject.toml + {{PYTHON}} -m uv pip compile --output-file=requirements-optional.txt --extra=optional pyproject.toml + +# Update dependencies +update_dependencies: + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements.txt pyproject.toml + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-dev.txt --extra=dev pyproject.toml + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-optional.txt --extra=optional pyproject.toml + +update_dependencies_quiet: + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements.txt pyproject.toml > /dev/null + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-dev.txt --extra=dev pyproject.toml > /dev/null + {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-optional.txt --extra=optional pyproject.toml > /dev/null + +# +# Packaging +# + +# Build package +build: install + {{PYTHON}} -m build + +# Create Git tag for release +create_tag tag notes="": + git tag -a v{{tag}} -m "Release {{tag}} "{{NEWLINE}}" {{notes}})" diff --git a/pyproject.toml b/pyproject.toml index 9dd6e66..c5c5088 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,36 +1,303 @@ [build-system] -requires = [ "setuptools >= 35.0.2", "wheel >= 0.29.0"] build-backend = "setuptools.build_meta" +requires = ["setuptools>=67.0", "wheel"] + +[project] +authors = [{ "name" = "R.Broderick" }] +description = "Python verbal based regular expressions" +version = "2.0.0" +license = { "file" = "LICENSE" } +name = "verbex" +readme = { file = "README.md", content-type = "text/markdown" } +dependencies = ["beartype", "typing-extensions; python_version < '3.12'"] +requires-python = ">=3.10.0" + +[project.optional-dependencies] +dev = [ + "build", + "dapperdata", + "glom", + "mypy", + "pytest", + "pytest-cov", + "pytest-pretty", + "ruamel.yaml", + "ruff", + "toml-sort", + "uv", + "validate-pyproject", + "packaging", + "snakeviz", + "pre-commit", + "tox", + "tox-pyenv-redux", + "pylint", + "perflint", + "snakeviz", + "pip-audit", +] + +optional = [] +docs = [ + "Sphinx", + "sphinx-autodoc-typehints", + "sphinx-rtd-theme", + "sphinx-rtd-size", + "autodocsumm", + "sphinx-pyproject", +] +[tool.dapperdata] +exclude_paths = [".venv", ".mypy_cache", ".git", ".vscode"] + +[tool.pylint.format] +max-line-length = 200 + +[tool.pylint.main] +extension-pkg-allow-list = ["pyodbc", "win32gui"] +load-plugins = "perflint" + +[tool.pylint."messages control"] +disable = [ + "W0707", + "W0703", + "C0204", + "C0411", + "C0114", + "C0115", + "C0116", + "W0611", + "E0401", + "W2301", + "C0414", + "C0413", + "R0902", + "R0914", + "W8205", + "E0611", + "C0103", + "R0913", + "R0903", + "W0613", + "C0412", + "W8201", + "R0912", + "R0915", + "R0801", + "W8402", + "W0511", + "W0622", + "W0107", + "R0911", + "E1101", + "E1136", + "E1120", + "W8403", + "W0222", + "E1129", + "E0213", + "W0221", + "E1128", + "C0321", + "logging-fstring-interpolation", + "unnecessary-lambda-assignment", + "protected-access", + # codes in ruff + "C0105", + "C0131", + "C0132", + "C0205", + "C0208", + "C0414", + "C3002", + "E0100", + "E0101", + "E0116", + "E0117", + "E0118", + "E0237", + "E0241", + "E0302", + "E0307", + "E0604", + "E0605", + "E1142", + "E1205", + "E1206", + "E1300", + "E1307", + "E1310", + "E1507", + "E1700", + "E2502", + "E2510", + "E2512", + "E2513", + "E2514", + "E2515", + "R0124", + "R0133", + "R0206", + "R0402", + "R0911", + "R0912", + "R0913", + "R0915", + "R1701", + "R1711", + "R1714", + "R1722", + # "R2004", + # "R5501", + "W0120", + "W0127", + "W0129", + "W0131", + "W0406", + "W0602", + "W0603", + "W0711", + "W1508", + "W1509", + "W1510", + # "W2901", + "W3301", +] + +[tool.pytest.ini_options] +pythonpath = ["src"] + +[tool.ruff] +exclude = [".venv"] +line-length = 88 +indent-width = 4 +target-version = "py310" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = true +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + +[tool.ruff.lint] +typing-modules = ["beartype.typing"] +select = ["ALL"] +ignore = [ + "B024", + "PIE790", + "T201", + "PYI013", + "ANN101", + "TCH003", + "PLC0414", + "ERA001", + "T203", + "ANN102", + "ANN401", + "TCH002", + "TD002", + "TD003", + "FIX002", + "D203", + "D213", + "COM812", + "ISC001", + "FBT001", + "FBT002", +] +fixable = ["ALL"] +unfixable = [] + +[tool.ruff.lint.isort] +force-single-line = true + +[tool.setuptools.dynamic] +readme = { file = ["README.md"] } + +[tool.setuptools.package-data] +library = ["py.typed"] + +[tool.setuptools.packages.find] +exclude = ["docs*", "tests*"] +where = ["src"] + +[tool.sphinx-pyproject] +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +coverage_show_missing_items = true +extensions = [ + "sphinx.ext.autodoc", + "sphinx_autodoc_typehints", + "sphinx.ext.viewcode", + "sphinx.ext.coverage", + "autodocsumm", + "sphinx_rtd_theme", + 'sphinx_rtd_size', +] +sphinx_rtd_size_width = "90%" +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +auto_doc_default_options = { 'autosummary' = true } +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_theme = "sphinx_rtd_theme" +html_style = "css/custom.css" +html_static_path = ["_static"] +html_theme_options = { 'display_version' = true, 'sticky_navigation' = true, 'includehidden' = true, 'titles_only' = false } +autosummary_generate = true + +[tool.sphinx-pyproject.autodoc_default_options] +exclude-members = """ + __weakref__, + __sizeof__, + __hash__, + __module__, + __dict__, + __annotations__, + __orig_bases__, + __parameters__, + __abstractmethods__, + __non_callable_proto_members__, + __protocol_attrs__, + __subclasshook__, + __dataclass_fields__, + __post_init__, + __dataclass_params__, + __match_args__, + __str__, + __repr__""" +members = true +member-order = 'bysource' +special-members = true +undoc-members = true [tool.tox] legacy_tox_ini = """ [tox] -envlist = py37, py38, py39, py310, py311 -isolated_build = true - -[gh-actions] -python = - 3.7: py37 - 3.8: py38 - 3.9: py39 - 3.10: py310 +skipsdist = True +isolated_build = True +envlist = py310, py311, py312 + [testenv] -setenv = - PYTHONPATH = {toxinidir} -deps = -r{toxinidir}/requirements_test.txt -commands = python -m unittest discover -v -""" +deps = + -rrequirements-dev.txt + -rrequirements-optional.txt -[tool.isort] -profile = "black" -sections = ["FUTURE","STDLIB","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"] - -[tool.mypy] -plugins = "pydantic.mypy" -follow_imports = "silent" -warn_redundant_casts = true -warn_unused_ignores = false -disallow_any_generics = true -check_untyped_defs = true -no_implicit_reexport = true -disallow_untyped_defs = true +commands = + pytest tests +""" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a957fff..0000000 --- a/setup.cfg +++ /dev/null @@ -1,27 +0,0 @@ -[metadata] -name = Verbex -version = 1.2.0 -author = Victor Titor, Yan Wenjun, diogobeda, Mihai Ionut Vilcu, Peder Soholt, Sameer Raghuram, Kharms, Richard Broderick -license = GPLv3 -description = Make difficult regular expressions easy! Python fork based on of the awesome VerbalExpressions repo - https://github.com/jehna/VerbalExpressions -url = https://github.com/rbroderi/Verbex -long_description = file: README.md -long_description_content_type = text/markdown -classifiers = - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Programming Language :: Python - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Software Development :: Libraries - Topic :: Text Processing - -[options] -packages = verbex -include_package_data = True - -[options.extras_require] -testing = - tox diff --git a/setup.py b/setup.py deleted file mode 100755 index 7f1a176..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from setuptools import setup - -if __name__ == "__main__": - setup() diff --git a/verbex/__init__.py b/src/verbex/__init__.py similarity index 100% rename from verbex/__init__.py rename to src/verbex/__init__.py diff --git a/verbex/py.typed b/src/verbex/py.typed similarity index 100% rename from verbex/py.typed rename to src/verbex/py.typed diff --git a/verbex/verbex.py b/src/verbex/verbex.py similarity index 56% rename from verbex/verbex.py rename to src/verbex/verbex.py index 1a2d9ac..9f7a3f5 100644 --- a/verbex/verbex.py +++ b/src/verbex/verbex.py @@ -1,39 +1,37 @@ """Generate regular expressions from an easier fluent verbal form.""" + from __future__ import annotations import re +from collections.abc import Callable +from collections.abc import Iterator from enum import Enum from functools import wraps +from typing import Annotated +from typing import Any +from typing import TypeAlias +from typing import Union +from typing import cast +from typing import runtime_checkable try: - from typing import ( # <--------------- if Python ≥ 3.9.0 - Annotated, - ParamSpec, - Protocol, - TypeAlias, - runtime_checkable, - ) + from typing import Self except ImportError: - from typing_extensions import TypeAlias, Protocol, Annotated, ParamSpec, runtime_checkable # type: ignore # <--- if Python < 3.9.0 # noqa E501 + from typing_extensions import Self + +try: + from typing import ParamSpec + from typing import Protocol -from typing import TYPE_CHECKING, Pattern, TypeVar +except ImportError: + from typing_extensions import ParamSpec + from typing_extensions import Protocol -if TYPE_CHECKING: - from typing import Protocol # noqa: F811 +from re import Pattern +from typing import TypeVar -from beartype import beartype # type: ignore -from beartype.typing import ( # type: ignore - Any, - Callable, - Dict, - Iterator, - List, - Optional, - Tuple, - Union, - cast, -) -from beartype.vale import Is # type: ignore +from beartype import beartype +from beartype.vale import Is def _string_len_is_1(text: object) -> bool: @@ -43,8 +41,8 @@ def _string_len_is_1(text: object) -> bool: Char = Annotated[str, Is[_string_len_is_1]] -P = ParamSpec("P") # noqa: VNE001 -R = TypeVar("R") # noqa: VNE001 +P = ParamSpec("P") +R = TypeVar("R") # work around for bug https://github.com/python/mypy/issues/12660 @@ -56,8 +54,10 @@ class HasIter(Protocol): def __iter__(self) -> Iterator[Any]: """Object can be iterated. - Yields: + Yields + ------ Next object. + """ ... @@ -68,11 +68,11 @@ def __iter__(self) -> Iterator[Any]: class HasItems(Protocol): """Workaround for mypy P.kwargs.""" - def items(self) -> Tuple[str, Any]: + def items(self) -> tuple[str, Any]: """Object has items method. - Returns: - The dict of items. + :returns: The dict of items. + :rtype: dict """ ... @@ -80,18 +80,21 @@ def items(self) -> Tuple[str, Any]: class EscapedText(str): """Text that has been escaped for regex. - Arguments: - str -- Extend the string class. + :param str value: the string to escape + :return: escaped regex string + :rtype: str + """ - def __new__(cls, value: str) -> EscapedText: - """Return a escaped regex string. + __slots__ = () + + def __new__(cls, value: str) -> Self: + """Return an escaped regex string. - Arguments: - value -- the string to escape + :param str value: the string to escape + :return: escaped regex string + :rtype: str - Returns: - _description_ """ return str.__new__(cls, re.escape(value)) @@ -99,17 +102,17 @@ def __new__(cls, value: str) -> EscapedText: def re_escape(func: Callable[P, R]) -> Callable[P, R]: """Automatically escape any string parameters as EscapedText. - Arguments: - func -- The function to decorate. + :param func: The function to decorate. + :type func: Callable[P, R] + :return: The decorated function. + :rtype: Callable[P, R] - Returns: - The decorated function. """ @wraps(func) - def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore - escaped_args: List[Any] = [] - escaped_kwargs: Dict[str, Any] = {} + def inner(*args: P.args, **kwargs: P.kwargs) -> R: + escaped_args: list[Any] = [] + escaped_kwargs: dict[str, Any] = {} for arg in cast(HasIter, args): if not isinstance(arg, EscapedText) and isinstance(arg, str): escaped_args.append(EscapedText(arg)) @@ -122,7 +125,7 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore escaped_kwargs[arg_k] = EscapedText(str(arg_v)) else: escaped_kwargs[arg_k] = arg_v - return func(*escaped_args, **escaped_kwargs) # type: ignore + return func(*escaped_args, **escaped_kwargs) # pyright: ignore[reportCallIssue] return inner @@ -130,8 +133,9 @@ def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore class CharClass(Enum): """Enum of character classes in regex. - Arguments: - Enum -- Extends the Enum class. + :param Enum: Extends the Enum class. + :type Enum: class + """ DIGIT = "\\d" @@ -144,17 +148,19 @@ class CharClass(Enum): def __str__(self) -> str: """To string method based on Enum value. - Returns: - value of Enum + :return: value of Enum + :rtype: str + """ return self.value class SpecialChar(Enum): - """Enum of special charaters, shorthand. + """Enum of special characters, shorthand. + + :param Enum: Extends the Enum class. + :type Enum: class - Arguments: - Enum -- Extends the Enum class. """ # does not work / should not be used in [ ] @@ -166,77 +172,74 @@ class SpecialChar(Enum): def __str__(self) -> str: """To string for special chars enum. - Returns: - Return value of enum as string. + :return: Return value of enum as string. + :rtype: str + """ return self.value -CharClassOrChars: TypeAlias = Union[str, CharClass] -EscapedCharClassOrSpecial: TypeAlias = Union[str, CharClass, SpecialChar] +CharClassOrChars: TypeAlias = str | CharClass +EscapedCharClassOrSpecial: TypeAlias = str | CharClass | SpecialChar VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] -def _poseur_decorator(*poseur: Any) -> Any: - """Positional-only arguments runtime checker.""" - import functools - - def caller(func: Callable[P, R]) -> Callable[P, R]: # type: ignore - @functools.wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: - poseur_args = set(poseur).intersection(kwargs) # type: ignore - if poseur_args: - raise TypeError( - "%s() got some positional-only arguments passed as keyword" - " arguments: %r" % (func.__name__, ", ".join(poseur_args)), - ) - return func(*args, **kwargs) # type: ignore - - return wrapper - - return caller +class Verbex: + """VerbalExpressions class. + The following methods do not try to match the original js lib! -class Verbex: - """ - VerbalExpressions class. + .. note:: + This class is a modified version of the VerbalExpressions library. - the following methods do not try to match the original js lib! """ EMPTY_REGEX_FLAG = re.RegexFlag(0) @re_escape @beartype - def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG): + def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG) -> None: """Create a Verbex object; setting any needed flags. - Keyword Arguments: - modifiers -- Regex modifying flags (default: {re.RegexFlag(0)}) + :param modifiers: Regex modifying flags (default: ``re.RegexFlag(0)``) + :type modifiers: re.RegexFlag + + :returns: The created Verbex object. + :rtype: Verbex + """ # self._parts: List[str] = [text] - self._parts: List[str] = [] + self._parts: list[str] = [] self._modifiers = modifiers @property def modifiers(self) -> re.RegexFlag: """Return the modifiers for this Verbex object. - Returns: - The modifiers applied to this object. + :return: The modifiers applied to this object. + :rtype: re.RegexFlag + """ return self._modifiers def __str__(self) -> str: - """Return regex string representation.""" + """Return regex string representation. + + :return: The regex string representation. + :rtype: str + + """ return "".join(self._parts) @beartype - def _add(self, value: Union[str, List[str]]) -> Verbex: - """ - Append a transformed value to internal expression to be compiled. + def _add(self, value: str | list[str]) -> Verbex: + """Append a transformed value to internal expression to be compiled. As possible, this method should be "private". + + :return: Modified Verbex object. + :rtype: Verbex + """ if isinstance(value, list): self._parts.extend(value) @@ -245,7 +248,12 @@ def _add(self, value: Union[str, List[str]]) -> Verbex: return self def regex(self) -> Pattern[str]: - """Get a regular expression object.""" + """Get a regular expression object. + + :return: A regular expression object. + :rtype: Pattern[str] + + """ return re.compile( str(self), self._modifiers, @@ -260,7 +268,7 @@ def _capture_group_with_name( name: str, text: VerbexEscapedCharClassOrSpecial, ) -> Verbex: - return self._add(f"(?<{name}>{str(text)})") + return self._add(f"(?<{name}>{text!s})") @re_escape @beartype @@ -268,29 +276,29 @@ def _capture_group_without_name( self, text: VerbexEscapedCharClassOrSpecial, ) -> Verbex: - return self._add(f"({str(text)})") + return self._add(f"({text!s})") @re_escape @beartype - @_poseur_decorator("self") def capture_group( self, - name_or_text: Union[Optional[str], VerbexEscapedCharClassOrSpecial] = None, - text: Optional[VerbexEscapedCharClassOrSpecial] = None, + name_or_text: str | None | VerbexEscapedCharClassOrSpecial = None, + text: VerbexEscapedCharClassOrSpecial | None = None, ) -> Verbex: """Create a capture group. - Name is optional if not specified then the first argument is the text. + Name is optional. If not specified, then the first argument is the text. - Keyword Arguments: - name_or_text -- The name of the group / text to search for (default: {None}) - text -- The text to search for (default: {None}) + :param name_or_text: The name of the group / text to search for (default: None) + :type name_or_text: str or None + :param text: The text to search for (default: None) + :type text: str or None - Raises: - ValueError: If name is specified then text must be as well. + :raises ValueError: If name is specified, then text must be as well. + + :returns: Verbex with added capture group. + :rtype: Verbex - Returns: - Verbex with added capture group. """ if name_or_text is not None: if text is None: @@ -298,18 +306,20 @@ def capture_group( return self._capture_group_without_name(_text) if isinstance(name_or_text, str): return self._capture_group_with_name(name_or_text, text) - raise ValueError("text must be specified with optional name") + msg = "text must be specified with optional name" + raise ValueError(msg) @re_escape @beartype def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 """`or` is a python keyword so we use `OR` instead. - Arguments: - text -- Text to find or a Verbex object. + :param text: Text to find or a Verbex object. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add("|").find(text) @@ -318,102 +328,110 @@ def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """Find the text or Verbex object zero or more times. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)})*") + return self._add(f"(?:{text!s})*") @re_escape @beartype def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """Find the text or Verbex object one or more times. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)})+") + return self._add(f"(?:{text!s})+") @re_escape @beartype def n_times( self, text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 + n: int, ) -> Verbex: """Find the text or Verbex object n or more times. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)}){{{n}}}") + return self._add(f"(?:{text!s}){{{n}}}") @re_escape @beartype def n_times_or_more( self, text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 + n: int, ) -> Verbex: """Find the text or Verbex object at least n times. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)}){{{n},}}") + return self._add(f"(?:{text!s}){{{n},}}") @re_escape @beartype def n_to_m_times( self, text: VerbexEscapedCharClassOrSpecial, - n: int, # noqa: VNE001 - m: int, # noqa: VNE001 + n: int, + m: int, ) -> Verbex: """Find the text or Verbex object between n and m times. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + :param n: The minimum number of times to find the text. + :type n: int + :param m: The maximum number of times to find the text. + :type m: int + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)}){{{n},{m}}}") + return self._add(f"(?:{text!s}){{{n},{m}}}") @re_escape @beartype def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """Possibly find the text / Verbex object. - Arguments: - text -- The text / Verbex object to possibly find. + :param text: The text / Verbex object to possibly find. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ - return self._add(f"(?:{str(text)})?") + return self._add(f"(?:{text!s})?") @re_escape @beartype def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """Find the text or Verbex object. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(str(text)) @@ -422,11 +440,12 @@ def find(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: def then(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: """Synonym for find. - Arguments: - text -- The text / Verbex object to look for. + :param text: The text / Verbex object to look for. + :type text: VerbexEscapedCharClassOrSpecial + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self.find(text) @@ -437,8 +456,9 @@ def followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: Positive lookahead - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add(f"(?={text})") @@ -449,8 +469,9 @@ def not_followed_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: Negative lookahead - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add(f"(?!{text})") @@ -461,8 +482,9 @@ def preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: Positive lookbehind - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add(f"(?<={text})") @@ -473,23 +495,23 @@ def not_preceded_by(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: Negative Lookbehind - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add(f"(? Verbex: """Find anything in this group of chars or char class. - Arguments: - text -- The characters to look for. + :param text: The characters to look for. + :type text: str + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(f"(?:[{chargroup}])") @@ -498,11 +520,12 @@ def any_of(self, chargroup: CharClassOrChars) -> Verbex: def not_any_of(self, text: CharClassOrChars) -> Verbex: """Find anything but this group of chars or char class. - Arguments: - text -- The characters to not look for. + :param text: The characters to not look for. + :type text: str + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(f"(?:[^{text}])") @@ -510,11 +533,12 @@ def not_any_of(self, text: CharClassOrChars) -> Verbex: def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: """Find anything one or more times but this group of chars or char class. - Arguments: - text -- The characters to not look for. + :param text: The characters to not look for. + :type text: str + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(f"[^{chargroup}]+") @@ -523,48 +547,54 @@ def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: def start_of_line(self) -> Verbex: """Find the start of the line. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self.find(SpecialChar.START_OF_LINE) def end_of_line(self) -> Verbex: """Find the end of the line. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self.find(SpecialChar.END_OF_LINE) def line_break(self) -> Verbex: """Find a line break. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self.find(SpecialChar.LINEBREAK) def tab(self) -> Verbex: """Find a tab. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self.find(SpecialChar.TAB) def anything(self) -> Verbex: - """Find anything one or more time. + """Find anything one or more times. + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(".+") def as_few(self) -> Verbex: """Modify previous search to not be greedy. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add("?") @@ -572,12 +602,13 @@ def as_few(self) -> Verbex: def number_range(self, start: int, end: int) -> Verbex: """Generate a range of numbers. - Arguments: - start -- Start of the range - end -- End of the range + :param start: Start of the range + :type start: int + :param end: End of the range + :type end: int + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") @@ -585,20 +616,22 @@ def number_range(self, start: int, end: int) -> Verbex: def letter_range(self, start: Char, end: Char) -> Verbex: """Generate a range of letters. - Arguments: - start -- Start of the range - end -- End of the range + :param start: Start of the range + :type start: Char + :param end: End of the range + :type end: Char + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ return self._add(f"[{start}-{end}]") def word(self) -> Verbex: """Find a word on word boundary. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ return self._add("(\\b\\w+\\b)") @@ -607,17 +640,19 @@ def word(self) -> Verbex: def with_any_case(self) -> Verbex: """Modify Verbex object to be case insensitive. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ self._modifiers |= re.IGNORECASE return self def search_by_line(self) -> Verbex: - """Search each line, ^ and $ match begining and end of line respectively. + """Search each line, ^ and $ match beginning and end of line respectively. + + :return: Modified Verbex object. + :rtype: Verbex - Returns: - Modified Verbex object. """ self._modifiers |= re.MULTILINE return self @@ -625,8 +660,9 @@ def search_by_line(self) -> Verbex: def with_ascii(self) -> Verbex: """Match ascii instead of unicode. - Returns: - Modified Verbex object. + :return: Modified Verbex object. + :rtype: Verbex + """ self._modifiers |= re.ASCII return self diff --git a/verbex/GPLv3_LICENSE.txt b/verbex/GPLv3_LICENSE.txt deleted file mode 100644 index f288702..0000000 --- a/verbex/GPLv3_LICENSE.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/verbex/LICENSE.txt b/verbex/LICENSE.txt deleted file mode 100644 index 4b5ad82..0000000 --- a/verbex/LICENSE.txt +++ /dev/null @@ -1,39 +0,0 @@ -Verbal Expressions -Copyright (C) 2022 Richard Broderick - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - - This file incorporates work covered by the following copyright and - permission notice: - - The MIT License (MIT) - - Copyright (c) 2017 jehna - - 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. From 1ce407aabcabf2320ab8847e15286165c966f84b Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 01:17:30 -0400 Subject: [PATCH 65/90] cleanup and move to new dev enviroment. Drop support for python versions < 3.10 --- justfile | 2 +- pyproject.toml | 330 +++++++++++++++++++------------------- requirements-dev.txt | 194 ++++++++++++++++++++++ requirements-optional.txt | 3 + requirements.txt | 13 +- src/verbex/__init__.py | 12 +- src/verbex/verbex.py | 12 +- tests/__init__.py | 1 + tests/test_verbex.py | 9 +- 9 files changed, 382 insertions(+), 194 deletions(-) create mode 100644 requirements-dev.txt create mode 100644 requirements-optional.txt diff --git a/justfile b/justfile index 7856ca7..3d29723 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,5 @@ set ignore-comments -PACKAGE_SLUG := "src/precommithooks" +PACKAGE_SLUG := "src/verbex" export PYTHON_VERSION := if env("CI","false") != "false" { `python --version|cut -d" " -f2` } else { `cat .python-version` } PYTHON := if env("USE_SYSTEM_PYTHON", "false") != "false" { "python" } else { ".venv/bin/python" } PYTHON_ENV := if env("USE_SYSTEM_PYTHON", "false") != "false" { "" } else { "sh .venv/bin/activate &&" } diff --git a/pyproject.toml b/pyproject.toml index c5c5088..39d4525 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,49 +3,49 @@ build-backend = "setuptools.build_meta" requires = ["setuptools>=67.0", "wheel"] [project] -authors = [{ "name" = "R.Broderick" }] +authors = [{"name" = "R.Broderick"}] description = "Python verbal based regular expressions" version = "2.0.0" -license = { "file" = "LICENSE" } +license = {"file" = "LICENSE"} name = "verbex" -readme = { file = "README.md", content-type = "text/markdown" } +readme = {file = "README.md", content-type = "text/markdown"} dependencies = ["beartype", "typing-extensions; python_version < '3.12'"] requires-python = ">=3.10.0" [project.optional-dependencies] dev = [ - "build", - "dapperdata", - "glom", - "mypy", - "pytest", - "pytest-cov", - "pytest-pretty", - "ruamel.yaml", - "ruff", - "toml-sort", - "uv", - "validate-pyproject", - "packaging", - "snakeviz", - "pre-commit", - "tox", - "tox-pyenv-redux", - "pylint", - "perflint", - "snakeviz", - "pip-audit", + "build", + "dapperdata", + "glom", + "mypy", + "pytest", + "pytest-cov", + "pytest-pretty", + "ruamel.yaml", + "ruff", + "toml-sort", + "uv", + "validate-pyproject", + "packaging", + "snakeviz", + "pre-commit", + "tox", + "tox-pyenv-redux", + "pylint", + "perflint", + "snakeviz", + "pip-audit" ] - optional = [] docs = [ - "Sphinx", - "sphinx-autodoc-typehints", - "sphinx-rtd-theme", - "sphinx-rtd-size", - "autodocsumm", - "sphinx-pyproject", + "Sphinx", + "sphinx-autodoc-typehints", + "sphinx-rtd-theme", + "sphinx-rtd-size", + "autodocsumm", + "sphinx-pyproject" ] + [tool.dapperdata] exclude_paths = [".venv", ".mypy_cache", ".git", ".vscode"] @@ -58,109 +58,109 @@ load-plugins = "perflint" [tool.pylint."messages control"] disable = [ - "W0707", - "W0703", - "C0204", - "C0411", - "C0114", - "C0115", - "C0116", - "W0611", - "E0401", - "W2301", - "C0414", - "C0413", - "R0902", - "R0914", - "W8205", - "E0611", - "C0103", - "R0913", - "R0903", - "W0613", - "C0412", - "W8201", - "R0912", - "R0915", - "R0801", - "W8402", - "W0511", - "W0622", - "W0107", - "R0911", - "E1101", - "E1136", - "E1120", - "W8403", - "W0222", - "E1129", - "E0213", - "W0221", - "E1128", - "C0321", - "logging-fstring-interpolation", - "unnecessary-lambda-assignment", - "protected-access", - # codes in ruff - "C0105", - "C0131", - "C0132", - "C0205", - "C0208", - "C0414", - "C3002", - "E0100", - "E0101", - "E0116", - "E0117", - "E0118", - "E0237", - "E0241", - "E0302", - "E0307", - "E0604", - "E0605", - "E1142", - "E1205", - "E1206", - "E1300", - "E1307", - "E1310", - "E1507", - "E1700", - "E2502", - "E2510", - "E2512", - "E2513", - "E2514", - "E2515", - "R0124", - "R0133", - "R0206", - "R0402", - "R0911", - "R0912", - "R0913", - "R0915", - "R1701", - "R1711", - "R1714", - "R1722", - # "R2004", - # "R5501", - "W0120", - "W0127", - "W0129", - "W0131", - "W0406", - "W0602", - "W0603", - "W0711", - "W1508", - "W1509", - "W1510", - # "W2901", - "W3301", + "W0707", + "W0703", + "C0204", + "C0411", + "C0114", + "C0115", + "C0116", + "W0611", + "E0401", + "W2301", + "C0414", + "C0413", + "R0902", + "R0914", + "W8205", + "E0611", + "C0103", + "R0913", + "R0903", + "W0613", + "C0412", + "W8201", + "R0912", + "R0915", + "R0801", + "W8402", + "W0511", + "W0622", + "W0107", + "R0911", + "E1101", + "E1136", + "E1120", + "W8403", + "W0222", + "E1129", + "E0213", + "W0221", + "E1128", + "C0321", + "logging-fstring-interpolation", + "unnecessary-lambda-assignment", + "protected-access", + # codes in ruff + "C0105", + "C0131", + "C0132", + "C0205", + "C0208", + "C0414", + "C3002", + "E0100", + "E0101", + "E0116", + "E0117", + "E0118", + "E0237", + "E0241", + "E0302", + "E0307", + "E0604", + "E0605", + "E1142", + "E1205", + "E1206", + "E1300", + "E1307", + "E1310", + "E1507", + "E1700", + "E2502", + "E2510", + "E2512", + "E2513", + "E2514", + "E2515", + "R0124", + "R0133", + "R0206", + "R0402", + "R0911", + "R0912", + "R0913", + "R0915", + "R1701", + "R1711", + "R1714", + "R1722", + # "R2004", + # "R5501", + "W0120", + "W0127", + "W0129", + "W0131", + "W0406", + "W0602", + "W0603", + "W0711", + "W1508", + "W1509", + "W1510", + # "W2901", + "W3301" ] [tool.pytest.ini_options] @@ -198,27 +198,27 @@ docstring-code-line-length = "dynamic" typing-modules = ["beartype.typing"] select = ["ALL"] ignore = [ - "B024", - "PIE790", - "T201", - "PYI013", - "ANN101", - "TCH003", - "PLC0414", - "ERA001", - "T203", - "ANN102", - "ANN401", - "TCH002", - "TD002", - "TD003", - "FIX002", - "D203", - "D213", - "COM812", - "ISC001", - "FBT001", - "FBT002", + "B024", + "PIE790", + "T201", + "PYI013", + "ANN101", + "TCH003", + "PLC0414", + "ERA001", + "T203", + "ANN102", + "ANN401", + "TCH002", + "TD002", + "TD003", + "FIX002", + "D203", + "D213", + "COM812", + "ISC001", + "FBT001", + "FBT002" ] fixable = ["ALL"] unfixable = [] @@ -227,7 +227,7 @@ unfixable = [] force-single-line = true [tool.setuptools.dynamic] -readme = { file = ["README.md"] } +readme = {file = ["README.md"]} [tool.setuptools.package-data] library = ["py.typed"] @@ -241,24 +241,24 @@ where = ["src"] # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration coverage_show_missing_items = true extensions = [ - "sphinx.ext.autodoc", - "sphinx_autodoc_typehints", - "sphinx.ext.viewcode", - "sphinx.ext.coverage", - "autodocsumm", - "sphinx_rtd_theme", - 'sphinx_rtd_size', + "sphinx.ext.autodoc", + "sphinx_autodoc_typehints", + "sphinx.ext.viewcode", + "sphinx.ext.coverage", + "autodocsumm", + "sphinx_rtd_theme", + 'sphinx_rtd_size' ] sphinx_rtd_size_width = "90%" templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -auto_doc_default_options = { 'autosummary' = true } +auto_doc_default_options = {'autosummary' = true} # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" html_style = "css/custom.css" html_static_path = ["_static"] -html_theme_options = { 'display_version' = true, 'sticky_navigation' = true, 'includehidden' = true, 'titles_only' = false } +html_theme_options = {'display_version' = true, 'sticky_navigation' = true, 'includehidden' = true, 'titles_only' = false} autosummary_generate = true [tool.sphinx-pyproject.autodoc_default_options] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..1c12bc0 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,194 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --output-file=requirements-dev.txt --extra=dev pyproject.toml +annotated-types==0.6.0 + # via pydantic +astroid==3.1.0 + # via pylint +attrs==23.2.0 + # via glom +beartype==0.18.5 +boltons==24.0.0 + # via + # face + # glom +boolean-py==4.0 + # via license-expression +build==1.2.1 +cachecontrol==0.14.0 + # via pip-audit +cachetools==5.3.3 + # via tox +certifi==2024.2.2 + # via requests +cfgv==3.4.0 + # via pre-commit +chardet==5.2.0 + # via tox +charset-normalizer==3.3.2 + # via requests +click==8.1.7 + # via typer +colorama==0.4.6 + # via tox +coverage==7.5.0 + # via pytest-cov +cyclonedx-python-lib==6.4.4 + # via pip-audit +dapperdata==0.4.0 +defusedxml==0.7.1 + # via py-serializable +dill==0.3.8 + # via pylint +distlib==0.3.8 + # via virtualenv +face==20.1.1 + # via glom +fastjsonschema==2.19.1 + # via validate-pyproject +filelock==3.13.4 + # via + # cachecontrol + # tox + # virtualenv +glom==23.5.0 +html5lib==1.1 + # via pip-audit +identify==2.5.36 + # via pre-commit +idna==3.7 + # via requests +iniconfig==2.0.0 + # via pytest +isort==5.13.2 + # via pylint +license-expression==30.3.0 + # via cyclonedx-python-lib +markdown-it-py==3.0.0 + # via rich +mccabe==0.7.0 + # via pylint +mdurl==0.1.2 + # via markdown-it-py +msgpack==1.0.8 + # via cachecontrol +mypy==1.10.0 +mypy-extensions==1.0.0 + # via mypy +nodeenv==1.8.0 + # via pre-commit +packageurl-python==0.15.0 + # via cyclonedx-python-lib +packaging==24.0 + # via + # build + # pip-audit + # pip-requirements-parser + # pyproject-api + # pytest + # tox +perflint==0.8.1 +pip==24.0 + # via pip-api +pip-api==0.0.33 + # via pip-audit +pip-audit==2.7.2 +pip-requirements-parser==32.0.1 + # via pip-audit +platformdirs==4.2.1 + # via + # pylint + # tox + # virtualenv +pluggy==1.5.0 + # via + # pytest + # tox +pre-commit==3.7.0 +py-serializable==1.0.3 + # via cyclonedx-python-lib +pydantic==2.7.1 + # via + # dapperdata + # pydantic-settings +pydantic-core==2.18.2 + # via pydantic +pydantic-settings==2.2.1 + # via dapperdata +pyenv-inspect==0.4.0 + # via virtualenv-pyenv +pygments==2.17.2 + # via rich +pylint==3.1.0 + # via perflint +pyparsing==3.1.2 + # via pip-requirements-parser +pyproject-api==1.6.1 + # via tox +pyproject-hooks==1.0.0 + # via build +pytest==8.2.0 + # via + # pytest-cov + # pytest-pretty +pytest-cov==5.0.0 +pytest-pretty==1.2.0 +python-dotenv==1.0.1 + # via pydantic-settings +pyyaml==6.0.1 + # via pre-commit +requests==2.31.0 + # via + # cachecontrol + # pip-audit +rich==13.7.1 + # via + # pip-audit + # pytest-pretty + # typer +ruamel-yaml==0.18.6 + # via dapperdata +ruamel-yaml-clib==0.2.8 + # via ruamel-yaml +ruff==0.4.2 +setuptools==69.5.1 + # via nodeenv +shellingham==1.5.4 + # via typer +six==1.16.0 + # via html5lib +snakeviz==2.2.0 +sortedcontainers==2.4.0 + # via cyclonedx-python-lib +toml==0.10.2 + # via pip-audit +toml-sort==0.23.1 +tomlkit==0.12.4 + # via + # pylint + # toml-sort +tornado==6.4 + # via snakeviz +tox==4.15.0 + # via tox-pyenv-redux +tox-pyenv-redux==1.1.0 +typer==0.12.3 + # via dapperdata +typing-extensions==4.11.0 + # via + # mypy + # pydantic + # pydantic-core + # typer +urllib3==2.2.1 + # via requests +uv==0.1.39 +validate-pyproject==0.16 +virtualenv==20.26.0 + # via + # pre-commit + # tox + # virtualenv-pyenv +virtualenv-pyenv==0.5.0 + # via tox-pyenv-redux +webencodings==0.5.1 + # via html5lib diff --git a/requirements-optional.txt b/requirements-optional.txt new file mode 100644 index 0000000..bbca596 --- /dev/null +++ b/requirements-optional.txt @@ -0,0 +1,3 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --output-file=requirements-optional.txt --extra=optional pyproject.toml +beartype==0.18.5 diff --git a/requirements.txt b/requirements.txt index d94285e..516c965 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,3 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile requirements.in -# -beartype==0.10.4 - # via -r requirements.in -typing-extensions==4.2.0 - # via -r requirements.in +# This file was autogenerated by uv via the following command: +# uv pip compile --output-file=requirements.txt pyproject.toml +beartype==0.18.5 diff --git a/src/verbex/__init__.py b/src/verbex/__init__.py index 9d5e731..6ce3539 100644 --- a/src/verbex/__init__.py +++ b/src/verbex/__init__.py @@ -1,10 +1,12 @@ -try: - from importlib.metadata import version -except ImportError: - from importlib_metadata import version # type: ignore +"""Verbal regular expression library.""" + +# try: +# from importlib.metadata import version +# except ImportError: +# from importlib_metadata import version from .verbex import CharClass as CharClass from .verbex import SpecialChar as SpecialChar from .verbex import Verbex as Verbex -__version__ = version("verbex") +# __version__ = version("verbex") diff --git a/src/verbex/verbex.py b/src/verbex/verbex.py index 9f7a3f5..033ab28 100644 --- a/src/verbex/verbex.py +++ b/src/verbex/verbex.py @@ -19,15 +19,9 @@ except ImportError: from typing_extensions import Self -try: - from typing import ParamSpec - from typing import Protocol - -except ImportError: - from typing_extensions import ParamSpec - from typing_extensions import Protocol - from re import Pattern +from typing import ParamSpec +from typing import Protocol from typing import TypeVar from beartype import beartype @@ -184,7 +178,7 @@ def __str__(self) -> str: VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] -class Verbex: +class Verbex: # pylint: disable=too-many-public-methods """VerbalExpressions class. The following methods do not try to match the original js lib! diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..aa0162f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for the Verbex package.""" diff --git a/tests/test_verbex.py b/tests/test_verbex.py index 9b7f81c..ca27a72 100644 --- a/tests/test_verbex.py +++ b/tests/test_verbex.py @@ -1,6 +1,7 @@ # pyright: reportPrivateUsage=false # flake8: noqa # type: ignore +# pylint: disable-all import re import unittest @@ -19,10 +20,10 @@ class verbexTest(unittest.TestCase): # # self.exp = None def test_should_render_verbex_as_string(self): - self.assertEqual(str(Verbex()._add("^$")), "^$") # noqa + self.assertEqual(str(Verbex()._add("^$")), "^$") def test_should_render_verbex_list_as_string(self): - self.assertEqual(str(Verbex()._add(["^", "[0-9]", "$"])), "^[0-9]$") # noqa + self.assertEqual(str(Verbex()._add(["^", "[0-9]", "$"])), "^[0-9]$") def test_should_match_characters_in_range(self): regex = Verbex().letter_range("a", "c").regex() @@ -45,14 +46,14 @@ def test_should_match_anything(self): regex = Verbex().anything().regex() self.assertIsNotNone(re.fullmatch(regex, "!@#$%¨&*()__+{}")) - def test_should_match_anything_but_specified_element_when_element_is_not_found( # noqa: E501 + def test_should_match_anything_but_specified_element_when_element_is_not_found( self, ): regex = Verbex().anything_but("X").find(" Files").regex() self.assertRegex("Y Files", regex) self.assertNotRegex("X Files", regex) - def test_should_not_match_anything_but_specified_element_when_specified_element_is_found( # noqa: E501 + def test_should_not_match_anything_but_specified_element_when_specified_element_is_found( self, ): regex = Verbex().anything_but("X").regex() From ff1d5a1e66a9526c8c3135efe7cb1112ddd54f04 Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 10:41:32 -0400 Subject: [PATCH 66/90] update readme --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e7daa8..095148b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ Verbex: Python verbal based regular expressions ================================================ -![Build Status](https://github.com/rbroderi/Verbex/actions/workflows/main.yml/badge.svg?event=push) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +[![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) -[![Generic badge](https://img.shields.io/badge/flake8-linted-yellow.svg)](https://github.com/pycqa/flake8) +![Static Badge](https://img.shields.io/badge/:badgeContent) +![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version) + ## Installation ```bash From c898467ab50dde861017bc7735ed738470fd34b7 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 10:43:04 -0400 Subject: [PATCH 67/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 095148b..93ac4d8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Verbex: Python verbal based regular expressions ================================================ - + [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) From 9436a14986b6afc33a0147a5b1fee2b615d7ae23 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 10:51:43 -0400 Subject: [PATCH 68/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93ac4d8..0d3eb5f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Verbex: Python verbal based regular expressions [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) -![Static Badge](https://img.shields.io/badge/:badgeContent) +[![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://shields.io/) ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version) From 3433381bb25489afa9b47a7836ac9f1ed9993d86 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 10:54:21 -0400 Subject: [PATCH 69/90] Update gh_pages.yaml --- .github/workflows/gh_pages.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml index ac6bda7..1a755b2 100644 --- a/.github/workflows/gh_pages.yaml +++ b/.github/workflows/gh_pages.yaml @@ -2,7 +2,7 @@ name: Deploy Sphinx documentation to Pages "on": push: - branches: [main] # branch to trigger deployment + branches: [master] # branch to trigger deployment jobs: pages: From d40c1eec980be4dc1de9904294450743b04e70b2 Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 10:58:49 -0400 Subject: [PATCH 70/90] update pypi workflow to publish --- .github/workflows/pypi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml index 241e314..0332979 100644 --- a/.github/workflows/pypi.yaml +++ b/.github/workflows/pypi.yaml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/project/protocol-implements-decorator/ # Replace with your PyPI project name + url: https://pypi.org/project/Verbex/ # Replace with your PyPI project name permissions: id-token: write # IMPORTANT: mandatory for trusted publishing From 18c63ac568b2b0e700508c0deb15dcd19cc7f3df Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 11:00:03 -0400 Subject: [PATCH 71/90] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 39d4525..8f8c570 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ requires = ["setuptools>=67.0", "wheel"] [project] authors = [{"name" = "R.Broderick"}] description = "Python verbal based regular expressions" -version = "2.0.0" +version = "2.0.1" license = {"file" = "LICENSE"} name = "verbex" readme = {file = "README.md", content-type = "text/markdown"} From b8c4fcab0cf1f8c3a35b42a27360a17dc56932cc Mon Sep 17 00:00:00 2001 From: "R. Broderick" Date: Sun, 28 Apr 2024 11:19:36 -0400 Subject: [PATCH 72/90] add classifiers --- pyproject.toml | 15 ++++++-- requirements.in | 3 -- requirements_dev.in | 5 --- requirements_dev.txt | 80 ------------------------------------------- requirements_test.txt | 4 --- 5 files changed, 13 insertions(+), 94 deletions(-) delete mode 100644 requirements.in delete mode 100644 requirements_dev.in delete mode 100644 requirements_dev.txt delete mode 100644 requirements_test.txt diff --git a/pyproject.toml b/pyproject.toml index 8f8c570..27a5520 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,12 +5,19 @@ requires = ["setuptools>=67.0", "wheel"] [project] authors = [{"name" = "R.Broderick"}] description = "Python verbal based regular expressions" -version = "2.0.1" +version = "2.0.2" license = {"file" = "LICENSE"} -name = "verbex" +name = "Verbex" readme = {file = "README.md", content-type = "text/markdown"} dependencies = ["beartype", "typing-extensions; python_version < '3.12'"] requires-python = ">=3.10.0" +classifiers = [ + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] [project.optional-dependencies] dev = [ @@ -46,6 +53,10 @@ docs = [ "sphinx-pyproject" ] +[project.urls] +homepage = "https://github.com/rbroderi/Verbex" +documentation = "https://rbroderi.github.io/Verbex/" + [tool.dapperdata] exclude_paths = [".venv", ".mypy_cache", ".git", ".vscode"] diff --git a/requirements.in b/requirements.in deleted file mode 100644 index 07ac3c6..0000000 --- a/requirements.in +++ /dev/null @@ -1,3 +0,0 @@ -beartype -typing-extensions -importlib-metadata; python_version < '3.7' diff --git a/requirements_dev.in b/requirements_dev.in deleted file mode 100644 index 31f1e1e..0000000 --- a/requirements_dev.in +++ /dev/null @@ -1,5 +0,0 @@ -tox -mypy>=0.950 -black -pre-commit -pdoc diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index ef02568..0000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,80 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.10 -# To update, run: -# -# pip-compile requirements_dev.in -# -black==22.3.0 - # via -r requirements_dev.in -cfgv==3.3.1 - # via pre-commit -click==8.1.3 - # via black -colorama==0.4.4 - # via - # click - # tox -distlib==0.3.4 - # via virtualenv -filelock==3.6.0 - # via - # tox - # virtualenv -identify==2.5.0 - # via pre-commit -jinja2==3.1.2 - # via pdoc -markupsafe==2.1.1 - # via - # jinja2 - # pdoc -mypy==0.950 - # via -r requirements_dev.in -mypy-extensions==0.4.3 - # via - # black - # mypy -nodeenv==1.6.0 - # via pre-commit -packaging==21.3 - # via tox -pathspec==0.9.0 - # via black -pdoc==11.2.0 - # via -r requirements_dev.in -platformdirs==2.5.2 - # via - # black - # virtualenv -pluggy==1.0.0 - # via tox -pre-commit==2.19.0 - # via -r requirements_dev.in -py==1.11.0 - # via tox -pygments==2.12.0 - # via pdoc -pyparsing==3.0.8 - # via packaging -pyyaml==6.0 - # via pre-commit -six==1.16.0 - # via - # tox - # virtualenv -toml==0.10.2 - # via - # pre-commit - # tox -tomli==2.0.1 - # via - # black - # mypy -tox==3.25.0 - # via -r requirements_dev.in -typing-extensions==4.2.0 - # via mypy -virtualenv==20.14.1 - # via - # pre-commit - # tox diff --git a/requirements_test.txt b/requirements_test.txt deleted file mode 100644 index 37fcfa7..0000000 --- a/requirements_test.txt +++ /dev/null @@ -1,4 +0,0 @@ -tox -typing-extensions -beartype -importlib-metadata; python_version < '3.7' From 75e672f72d8b8557102dcd306f0858a4bbba1f8d Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:35:46 -0400 Subject: [PATCH 73/90] Rename LICENSE.txt to LICENSE --- LICENSE.txt => LICENSE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE.txt => LICENSE (100%) diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE From df0e0e323d5ab2277a80f5559adda93948a83544 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:36:55 -0400 Subject: [PATCH 74/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d3eb5f..d9add4d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Verbex: Python verbal based regular expressions [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![PyPI license](https://img.shields.io/pypi/l/verbex)](https://www.gnu.org/licenses/gpl-3.0.en.html) +[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://raw.githubusercontent.com/rbroderi/Verbex/master/LICENSE) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) From 40b0bba1133c2cfb35696c13c9c586eccbbe3ee0 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:38:47 -0400 Subject: [PATCH 75/90] Update LICENSE --- LICENSE | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index ba81a44..942046e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,4 @@ -Verbal Expressions -Copyright (C) 2022 Richard Broderick - - GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. From 209b4694b389f6482032c3cd859d36860b906de6 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:40:19 -0400 Subject: [PATCH 76/90] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d9add4d..1e69064 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ Verbex: Python verbal based regular expressions [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://raw.githubusercontent.com/rbroderi/Verbex/master/LICENSE) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) From 429f9b6ab7be349436e7edff802a018536424398 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:40:31 -0400 Subject: [PATCH 77/90] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1e69064..b2a8762 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Verbex: Python verbal based regular expressions ================================================ +[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://raw.githubusercontent.com/rbroderi/Verbex/master/LICENSE) [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) From a71bedccab2894cc90676066f40270464b06ddde Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:52:20 -0400 Subject: [PATCH 78/90] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2a8762..0b061fb 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ Verbex: Python verbal based regular expressions ================================================ -[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://raw.githubusercontent.com/rbroderi/Verbex/master/LICENSE) +[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) -[![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://shields.io/) +[![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://github.com/astral-sh/uv) ![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version) From 214583fb96d7d4ec40acd4269456621a8ec517b0 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:53:24 -0400 Subject: [PATCH 79/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b061fb..5ff1bc3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Verbex: Python verbal based regular expressions [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) [![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://github.com/astral-sh/uv) -![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version) +[![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version](https://github.com/rbroderi/Verbex/releases) ## Installation From f2088f91ec7b3277fc5c5c4a8f9893454bb01c60 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 11:53:46 -0400 Subject: [PATCH 80/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ff1bc3..e206a76 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Verbex: Python verbal based regular expressions [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) [![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://github.com/astral-sh/uv) -[![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version](https://github.com/rbroderi/Verbex/releases) +[![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version)](https://github.com/rbroderi/Verbex/releases) ## Installation From 1cf8876f95bcf8c737da4520041177b518d471c5 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 12:04:05 -0400 Subject: [PATCH 81/90] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e206a76..bd587d8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Verbex: Python verbal based regular expressions ================================================ -[![GitHub License](https://img.shields.io/github/license/rbroderi/Verbex)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) +[![Generic badge](https://img.shields.io/badge/license-GPL‐3.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) + [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) From ff4b462cf9b2ae2bfc8d7a70490c1270ff25b80d Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 12:04:24 -0400 Subject: [PATCH 82/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bd587d8..d562698 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Verbex: Python verbal based regular expressions ================================================ -[![Generic badge](https://img.shields.io/badge/license-GPL‐3.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) +[![Generic badge](https://img.shields.io/badge/license-GPL‐3.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) From 90e4d49b955082ec71618306504abc1c8faacd7d Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 12:11:21 -0400 Subject: [PATCH 83/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d562698..57beb39 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Verbex: Python verbal based regular expressions [![Generic badge](https://img.shields.io/badge/license-GPL‐3.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/ansicolortags/) +[![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/Verbex/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) [![Generic badge](https://img.shields.io/badge/beartype-runtime_typed-cyan.svg)](https://github.com/beartype/beartype) [![Generic badge](https://img.shields.io/badge/bandit-checked-magenta.svg)](https://bandit.readthedocs.io/en/latest/) From c0d2172257bcac5f9278e6c36328f7feaf3d35f0 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 12:21:23 -0400 Subject: [PATCH 84/90] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 57beb39..d2e3c47 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ verbex = Verbex() ``` ## Documentation -[API](https://rbroderi.github.io/Verbex/verbex/verbex.html) +[API](https://rbroderi.github.io/Verbex/) ## Examples ### Testing if we have a valid URL From 63f202793db4b53258ec86b11394aed432c415d0 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Sun, 28 Apr 2024 12:22:03 -0400 Subject: [PATCH 85/90] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d2e3c47..dc51965 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,7 @@ print(result_re) ## Developer setup : running the tests ```bash -python setup.py develop -python setup.py test +just tests ``` ## Other implementations You can view all implementations on [VerbalExpressions.github.io](http://VerbalExpressions.github.io) From 2091da1b491a649f194265b5464733ef47adb1bf Mon Sep 17 00:00:00 2001 From: rbroderi Date: Fri, 10 Apr 2026 20:44:29 -0400 Subject: [PATCH 86/90] 3.0.0 release --- .github/dependabot.yml | 7 +- .github/workflows/black.yaml | 24 - .github/workflows/dapperdata.yaml | 24 - .github/workflows/gh_pages.yaml | 8 +- .github/workflows/lint-format.yml | 43 + .github/workflows/mypy.yaml | 24 - .github/workflows/pip-audit.yaml | 23 +- .github/workflows/publish-pypi.yml | 52 + .github/workflows/pypi.yaml | 93 -- .github/workflows/pytest.yaml | 27 - .github/workflows/quality-security.yml | 30 + .github/workflows/ruff.yaml | 24 - .github/workflows/tests.yml | 30 + .github/workflows/tomlsort.yaml | 27 - .github/workflows/tox.yaml | 44 +- .github/workflows/typecheck.yml | 30 + .markdownlint-cli2.yaml | 2 + .pre-commit-config.yaml | 278 ++-- .secrets.baseline | 127 ++ .vscode/settings.json | 5 +- CODE_OF_CONDUCT.md | 2 +- README.md | 40 +- ...ive-20220508115341-c1a945c08ab1170c.tar.gz | Bin 3764 -> 0 bytes ...ive-20220508115432-8f4db45c4efcdff6.tar.gz | Bin 3767 -> 0 bytes ...ive-20220508115930-3a2864bd92a8e920.tar.gz | Bin 3766 -> 0 bytes justfile | 100 +- pyproject.toml | 84 +- src/verbex/verbex.py | 406 +++-- tests/test_verbex.py | 835 ++++++---- uv.lock | 1469 +++++++++++++++++ 30 files changed, 2905 insertions(+), 953 deletions(-) delete mode 100644 .github/workflows/black.yaml delete mode 100644 .github/workflows/dapperdata.yaml create mode 100644 .github/workflows/lint-format.yml delete mode 100644 .github/workflows/mypy.yaml create mode 100644 .github/workflows/publish-pypi.yml delete mode 100644 .github/workflows/pypi.yaml delete mode 100644 .github/workflows/pytest.yaml create mode 100644 .github/workflows/quality-security.yml delete mode 100644 .github/workflows/ruff.yaml create mode 100644 .github/workflows/tests.yml delete mode 100644 .github/workflows/tomlsort.yaml create mode 100644 .github/workflows/typecheck.yml create mode 100644 .markdownlint-cli2.yaml create mode 100644 .secrets.baseline delete mode 100644 archive/archive-20220508115341-c1a945c08ab1170c.tar.gz delete mode 100644 archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz delete mode 100644 archive/archive-20220508115930-3a2864bd92a8e920.tar.gz create mode 100644 uv.lock diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 718572b..be70707 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,12 @@ version: 2 - updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + open-pull-requests-limit: 10 + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml deleted file mode 100644 index adc6081..0000000 --- a/.github/workflows/black.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Black Formatting - -"on": - push: - pull_request: - -jobs: - black: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Test Formatting - run: just ruff_format_fixes diff --git a/.github/workflows/dapperdata.yaml b/.github/workflows/dapperdata.yaml deleted file mode 100644 index f8c2a7f..0000000 --- a/.github/workflows/dapperdata.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Configuration File Formatting - -"on": - push: - pull_request: - -jobs: - dapperdata: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Test Formatting - run: just dapperdata_check diff --git a/.github/workflows/gh_pages.yaml b/.github/workflows/gh_pages.yaml index 1a755b2..939084e 100644 --- a/.github/workflows/gh_pages.yaml +++ b/.github/workflows/gh_pages.yaml @@ -1,9 +1,7 @@ name: Deploy Sphinx documentation to Pages - "on": push: - branches: [master] # branch to trigger deployment - + branches: [master] # branch to trigger deployment jobs: pages: runs-on: ubuntu-20.04 @@ -14,5 +12,5 @@ jobs: pages: write id-token: write steps: - - id: deployment - uses: sphinx-notes/pages@v3 + - id: deployment + uses: sphinx-notes/pages@v3 diff --git a/.github/workflows/lint-format.yml b/.github/workflows/lint-format.yml new file mode 100644 index 0000000..fc2d727 --- /dev/null +++ b/.github/workflows/lint-format.yml @@ -0,0 +1,43 @@ +name: Lint and Format +"on": + pull_request: + push: + branches: [main] + workflow_dispatch: +permissions: + contents: read +jobs: + lint-format: + name: Lint and format checks (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install project with dev dependencies + run: uv sync --extra dev + - name: Run lint and format hooks + run: | + uv run prek run ssort --all-files + uv run prek run fix-byte-order-marker check-merge-conflict \ + end-of-file-fixer trailing-whitespace mixed-line-ending --all-files + uv run prek run check-yaml check-toml check-added-large-files \ + check-executables-have-shebangs \ + check-shebang-scripts-are-executable \ + --all-files + uv run prek run yamlfmt yamllint --all-files + uv run prek run typos mdformat markdownlint-cli2 --all-files + uv run prek run taplo-format taplo-lint --all-files + uv run prek run pyupgrade pycln autopep695-format --all-files + uv run prek run ruff-check ruff-format ruff-check-post-format \ + refurb --all-files diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml deleted file mode 100644 index a2ed158..0000000 --- a/.github/workflows/mypy.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Mypy testing - -"on": - push: - pull_request: - -jobs: - mypy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Test Typing - run: just mypy diff --git a/.github/workflows/pip-audit.yaml b/.github/workflows/pip-audit.yaml index 4c9bfb2..0f3ed1e 100644 --- a/.github/workflows/pip-audit.yaml +++ b/.github/workflows/pip-audit.yaml @@ -1,23 +1,24 @@ name: Pip-Audit - "on": - push: pull_request: - + push: + branches: [main] + workflow_dispatch: jobs: selftest: + name: Pip-Audit (py${{ matrix.python-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version-file: .python-version - cache: 'pip' - + python-version: ${{ matrix.python-version }} + cache: "pip" - uses: taiki-e/install-action@just - - name: Install Dependencies run: just install - - uses: pypa/gh-action-pip-audit@v1.0.8 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..9019305 --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,52 @@ +name: Publish to PyPI +"on": + release: + types: [published] + workflow_dispatch: +permissions: + contents: read +jobs: + build: + name: Build distributions + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install check tooling + run: | + python -m pip install --upgrade pip + python -m pip install twine + - name: Build sdist and wheel + run: uv build --no-sources + - name: Check distributions + run: python -m twine check dist/* + - name: Upload distributions artifact + uses: actions/upload-artifact@v7 + with: + name: python-distributions + path: dist/ + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + contents: read + id-token: write + steps: + - name: Download distributions artifact + uses: actions/download-artifact@v8 + with: + name: python-distributions + path: dist/ + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Publish package to PyPI + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple dist/* diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml deleted file mode 100644 index 0332979..0000000 --- a/.github/workflows/pypi.yaml +++ /dev/null @@ -1,93 +0,0 @@ -name: Publish Python distribution to PyPI and TestPyPI - -"on": push - -jobs: - build: - name: Build distribution - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: python3 -m build - - name: Store the distribution packages - uses: actions/upload-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - publish-to-pypi: - name: >- - Publish Python distribution to PyPI - if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/project/Verbex/ # Replace with your PyPI project name - permissions: - id-token: write # IMPORTANT: mandatory for trusted publishing - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - github-release: - name: >- - Sign the Python distribution with Sigstore - and upload them to GitHub Release - needs: - - publish-to-pypi - runs-on: ubuntu-latest - - permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore - - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v2.1.1 - with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - name: Create GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - run: >- - gh release create - '${{ github.ref_name }}' - --repo '${{ github.repository }}' - --notes "" - - name: Upload artifact signatures to GitHub Release - env: - GITHUB_TOKEN: ${{ github.token }} - # Upload to GitHub Release using the `gh` CLI. - # `dist/` contains the built packages, and the - # sigstore-produced signatures and certificates. - run: >- - gh release upload - '${{ github.ref_name }}' dist/** - --repo '${{ github.repository }}' diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml deleted file mode 100644 index fc12c26..0000000 --- a/.github/workflows/pytest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: PyTest - -"on": - push: - pull_request: - -env: - COLUMNS: 120 - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Run Tests - run: just pytest diff --git a/.github/workflows/quality-security.yml b/.github/workflows/quality-security.yml new file mode 100644 index 0000000..a8b927f --- /dev/null +++ b/.github/workflows/quality-security.yml @@ -0,0 +1,30 @@ +name: Quality and Security +"on": + pull_request: + push: + branches: [main] + workflow_dispatch: +permissions: + contents: read +jobs: + quality-security: + name: Quality and security checks (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install project with dev dependencies + run: uv sync --extra dev + - name: Run quality and security hooks + run: uv run prek run vulture deptry detect-secrets --all-files diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml deleted file mode 100644 index 72009ea..0000000 --- a/.github/workflows/ruff.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Ruff Linting - -"on": - push: - pull_request: - -jobs: - black: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Test Formatting - run: just ruff_check diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..bf8f880 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Tests +"on": + pull_request: + push: + branches: [main] + workflow_dispatch: +permissions: + contents: read +jobs: + tests: + name: Test suite with coverage (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install project with dev dependencies + run: uv sync --extra dev + - name: Run coverage gate hook + run: uv run prek run coverage-100 --all-files diff --git a/.github/workflows/tomlsort.yaml b/.github/workflows/tomlsort.yaml deleted file mode 100644 index bf63104..0000000 --- a/.github/workflows/tomlsort.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: TOML Formatting - -"on": - push: - pull_request: - -jobs: - tomlsort: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - cache: 'pip' - - - uses: taiki-e/install-action@just - - - name: Install Dependencies - run: just install - - - name: Install toml-sort - run: pip install toml-sort - - - name: Test Typing - run: just tomlsort_check diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index c33f979..0c4386d 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,30 +1,30 @@ name: TOX testing - "on": - push: pull_request: - + push: + branches: [main] + workflow_dispatch: jobs: tox: runs-on: ubuntu-latest - env: - TOX_PARALLEL_NO_SPINNER: 1 # Removes logging spam + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.12" + tox-env: py312 + - python-version: "3.13" + tox-env: py313 + - python-version: "3.14" + tox-env: py314 steps: - - name: Checkout and setup Pythons - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - cache: 'pip' - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - uses: actions/setup-python@v5 + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 with: - python-version: '3.12' - cache: 'pip' - - name: Install tox and run tests - run: | - pip install tox - tox --parallel + python-version: ${{ matrix.python-version }} + - name: Install tox + run: python -m pip install --upgrade pip tox + - name: Run tox environment + run: tox -e ${{ matrix.tox-env }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 0000000..4864898 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,30 @@ +name: Typecheck +"on": + pull_request: + push: + branches: [main] + workflow_dispatch: +permissions: + contents: read +jobs: + typecheck: + name: Type analysis (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install project with dev dependencies + run: uv sync --extra dev + - name: Run type-check hooks + run: uv run prek run basedpyright ty-check --all-files diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 0000000..9358a7f --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,2 @@ +config: + MD013: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71c0181..e9ed331 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,158 +1,170 @@ -minimum_pre_commit_version: 1.21.0 +default_language_version: + python: python3.12 repos: - - repo: meta + - repo: local hooks: -# - id: check-hooks-apply - - id: check-useless-excludes - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + - id: prek-auto-update + name: prek-auto-update + entry: uv run prek auto-update + language: system + pass_filenames: false + always_run: false + stages: [manual] + fail_fast: true + - repo: https://github.com/bwhmather/ssort + rev: 0.16.0 hooks: - - id: check-ast - - id: check-json - - id: check-toml - - id: check-yaml + - id: ssort - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - - id: trailing-whitespace - types: [file, text] - exclude_types: [html, javascript] + - id: fix-byte-order-marker + - id: check-merge-conflict - id: end-of-file-fixer - types: [file, text] - exclude_types: [html, javascript] - - id: check-case-conflict + exclude: ^static/.*\.svg$ + - id: trailing-whitespace - id: mixed-line-ending - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: check-merge-conflict - name: "Check for merge conflicts" + exclude: ^static/.*\.svg$ - id: check-yaml - name: "Yaml: Check files" - types: [file, yaml] - id: check-toml - name: "TOML: check toml syntax" - types: [file, toml] - - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.2 - hooks: - - id: check-github-workflows - - repo: https://github.com/adrienverge/yamllint.git - rev: v1.35.1 # or higher tag + - id: check-added-large-files + exclude: ^tests/media/.*\.cbz$ + - id: debug-statements + language_version: python3.12 + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - repo: https://github.com/google/yamlfmt + rev: v0.21.0 + hooks: + - id: yamlfmt + files: \.(yml|yaml)$ + - repo: https://github.com/adrienverge/yamllint + rev: v1.38.0 hooks: - id: yamllint - name: "Yaml: Linting files" - args: [--format, parsable, --strict] - types: [file, yaml] - - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 - hooks: - - id: remove-tabs - name: "Python: Convert Tabs to 4 spaces" - args: ['--whitespaces-count', '4'] # defaults to: 4 - types: [file, python] + files: \.(yml|yaml)$ + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.11.0.1 + hooks: + - id: shellcheck + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.13.1-1 + hooks: + - id: shfmt + - repo: https://github.com/crate-ci/typos + rev: v1.45.0 + hooks: + - id: typos + - repo: https://github.com/executablebooks/mdformat + rev: 1.0.0 + hooks: + - id: mdformat + files: \.md$ + - repo: https://github.com/DavidAnson/markdownlint-cli2 + rev: v0.22.0 + hooks: + - id: markdownlint-cli2 + - repo: https://github.com/ComPWA/taplo-pre-commit + # toml formatter + + rev: v0.9.3 + hooks: + - id: taplo-format + - id: taplo-lint - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.21.2 hooks: - id: pyupgrade - name: "Python: upgrade syntax" - args: [--py310-plus] + language_version: python3.12 + args: [--py312-plus, --keep-runtime-typing] + files: ^(src|tests)/.*\.py$ - repo: https://github.com/hadialqattan/pycln - rev: v2.4.0 + rev: v2.6.0 hooks: - id: pycln - name: "Python: remove unused imports." - - repo: https://github.com/rbroderi/precommithooks - rev: v1.0.2 - hooks: - - id: python_file_name_check - name: "Python: File name check" - args: ["--ignore-test-files"] + language_version: python3.12 + args: [src, tests] + pass_filenames: false + always_run: true + stages: [pre-commit] - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.4.2 - hooks: - # Run the linter. - - id: ruff - name: "Python: Ruff" - types: [file, python] - args: [--fix] - fail_fast: true - # Run the formatter. + rev: v0.15.10 + hooks: + - id: ruff-check + language_version: python3.12 + args: [--fix, --exclude, typings] + files: ^(src|tests)/.*\.py$ - id: ruff-format - name: "Python: Ruff format" - types: [file, python] - fail_fast: true + language_version: python3.12 + args: [--exclude, typings] + files: ^(src|tests)/.*\.py$ + - id: ruff-check + name: ruff-check-post-format + language_version: python3.12 + args: [--exclude, typings] + files: ^(src|tests)/.*\.py$ - repo: local hooks: - - id: pylint - name: "Python: Pylint code with Perflint" - entry: python -m pylint + - id: autopep695-format + name: autopep695-format + entry: uvx autopep695 format language: system - types: [file, python] - args: [-rn, -sn, --load-plugins=perflint] - - repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 - hooks: - - id: add-trailing-comma - name: "Python: Add trailing comma" - types: [file, python] - - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.10.0' - hooks: - - id: mypy - name: "Python: Checking variable types" - args: [--ignore-missing-imports, --allow-redefinition] - exclude: "setup[.]py|conf[.]py" - additional_dependencies: - - pydantic - - types-all - - pandas-stubs - types: [file, python] - - repo: https://github.com/PyCQA/bandit - rev: '1.7.8' - hooks: - - id: bandit - name: "Python: Checking for potential security issues (bandit)" - args: - - "--skip=B404,B506,B607,B603,B701,B101,B602" - - repo: local - hooks: - - id: remove-en-dashes - name: Remove the EXTREMELY confusing unicode character U+2013 + files: ^(src|tests)/.*\.py$ + stages: [pre-commit] + - id: vulture + name: vulture + entry: | + uvx --python 3.12 vulture src --min-confidence 80 --ignore-names + dst,secure,httponly,samesite,unc_path,package_family_name,logo44x44 language: system - entry: perl -pi* -e 's/\xe2\x80\x93/-/g && ($t = 1) && print STDERR $_; END{{exit $t}}' - types: [file] - types_or: [python, powershell, lua, jinja] - - repo: https://github.com/sirosen/texthooks - rev: 0.6.6 - hooks: - - id: fix-smartquotes - types: [file] - types_or: [python, powershell, lua, jinja] - - id: fix-ligatures - types: [file] - types_or: [python, powershell, lua, jinja] - - id: forbid-bidi-controls - types: [file] - types_or: [python, powershell, lua, jinja] - fail_fast: true - - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.13.0 - hooks: - - id: pretty-format-java - args: [--autofix] - - id: pretty-format-golang - args: [--autofix] - - id: pretty-format-ini - args: [--autofix] - - id: pretty-format-rust - args: [--autofix] - - repo: local - hooks: - - id: fixes - name: fixes - entry: just _fixes_no_ruff + pass_filenames: false + always_run: true + stages: [pre-commit] + - id: deptry + name: deptry + entry: uv run deptry . --ignore DEP001,DEP002 + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + - id: refurb + name: refurb + entry: uvx ruff check --select FURB --ignore FURB110 src tests + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + - id: basedpyright + name: basedpyright + entry: uvx basedpyright + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + - id: ty-check + name: ty-check + entry: uvx ty check language: system pass_filenames: false - verbose: true + always_run: true + stages: [pre-commit] + - id: coverage-100 + name: coverage-100 + entry: | + uv run pytest --cov=src/verbex \ + --cov-report=term-missing --cov-fail-under=100 tests + language: system + pass_filenames: false + always_run: true + stages: [pre-commit] + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + language_version: python3.12 + args: + - "--baseline" + - ".secrets.baseline" + - "--exclude-files" + - "(^|[\\/])(\\.venv|\\.tox|dist|build|archive)([\\/]|$)" + stages: [pre-commit] diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..b0bd314 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,127 @@ +{ + "version": "1.5.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "DiscordBotTokenDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "name": "GitLabTokenDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "IPPublicDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "OpenAIDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "PypiTokenDetector" + }, + { + "name": "SendGridDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TelegramBotTokenDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + }, + { + "path": "detect_secrets.filters.heuristic.is_indirect_reference" + }, + { + "path": "detect_secrets.filters.heuristic.is_likely_id_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_lock_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_potential_uuid" + }, + { + "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign" + }, + { + "path": "detect_secrets.filters.heuristic.is_sequential_string" + }, + { + "path": "detect_secrets.filters.heuristic.is_swagger_file" + }, + { + "path": "detect_secrets.filters.heuristic.is_templated_secret" + } + ], + "results": {}, + "generated_at": "2026-04-11T00:43:40Z" +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3a46794..6103a5f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,12 +9,15 @@ "python.testing.pytestEnabled": false, "python.testing.unittestEnabled": true, "cSpell.words": [ + "chargroup", "pylance", "pyright", + "rtype", "Verbex" ], "python.linting.flake8Enabled": true, "python.linting.flake8Args": [ "--ignore=E501" - ] + ], + "python.analysis.typeCheckingMode": "off" } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a734e49..ad99aa3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,5 +1,5 @@ # Contributor Code of Conduct -This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. +This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage. diff --git a/README.md b/README.md index dc51965..c3a7bc2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -Verbex: Python verbal based regular expressions -================================================ +# Verbex: Python verbal based regular expressions + -[![Generic badge](https://img.shields.io/badge/license-GPL‐3.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) + +[![Generic badge](https://img.shields.io/badge/license-GPL%E2%80%903.0-orange.svg)](https://github.com/rbroderi/Verbex/blob/master/LICENSE) [![Code style: black](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![PyPI pyversions](https://img.shields.io/pypi/pyversions/verbex)](https://pypi.python.org/pypi/Verbex/) [![Generic badge](https://img.shields.io/badge/mypy-typed-purple.svg)](http://mypy-lang.org/) @@ -12,23 +13,38 @@ Verbex: Python verbal based regular expressions [![Generic badge](https://img.shields.io/badge/uv-requirements-yellow.svg)](https://github.com/astral-sh/uv) [![Dynamic TOML Badge](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Frbroderi%2FVerbex%2Fmaster%2Fpyproject.toml&query=%24.project.version&label=Version)](https://github.com/rbroderi/Verbex/releases) - ## Installation + ```bash pip install Verbex ``` + +## Notes + +- Verbex now depends on the `regex` package (instead of Python's built-in `re` engine) for faster and more expressive pattern evaluation. +- Quantifier methods default to possessive mode for performance (`maybe`, `zero_or_more`, `one_or_more`, `n_times`, `n_times_or_more`, `n_to_m_times`, `anything`). +- If you need legacy backtracking behavior, pass `possessive=False`. +- You can set the default quantifier behavior once per expression with `Verbex(default_possessive=False)`. +- `OR(...)` alternates only against the most recently added fragment. Use `or_expr(...)` to alternate against the full expression. + ## Usage + ```python from verbex import Verbex verbex = Verbex() ``` ## Documentation + [API](https://rbroderi.github.io/Verbex/) + ## Examples ### Testing if we have a valid URL + ```python +from verbex import Verbex + # Create an example of how to test for correctly formed URLs verbex = Verbex() tester = (verbex. @@ -45,30 +61,36 @@ tester = (verbex. test_url = "https://www.google.com" # Test if the URL is valid -if re.match(test_url.regex,test_url): +if tester.regex().fullmatch(test_url): print("Valid URL") # Print the generated regex -print(tester) # => ^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$ +print(tester) # Example: ^http?+://(?:www\.)?+[^ ]+$ ``` + ### Replacing strings + ```python +from verbex import Verbex + # Create a test string replace_me = "Replace bird with a duck" # Create an expression that looks for the word "bird" expression = Verbex().find('bird') -# Compile and use the regular expression using re -import re -regexp = expression.compile() +# Compile and use the regular expression +regexp = expression.regex() result_re = regexp.sub('duck', replace_me) print(result_re) ``` ## Developer setup : running the tests + ```bash just tests ``` + ## Other implementations + You can view all implementations on [VerbalExpressions.github.io](http://VerbalExpressions.github.io) diff --git a/archive/archive-20220508115341-c1a945c08ab1170c.tar.gz b/archive/archive-20220508115341-c1a945c08ab1170c.tar.gz deleted file mode 100644 index 2767ac9201f05820b1eea51e22cbe091f2066617..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3764 zcmV;l4omSLiwFpX=XYWP|6y`tXlZt3Eiy1NGB7nTI59CbGc++RV=-YlG&N%|IALNj zF*h({E_7jX0PS4ecH1@*&b7|zJD_qe#6+RxKc&&eZCyu6e45xkvYKv><+2b7S&S)? z1xedl#}BX%vM;tzvNHfkf|O**S<6jR^TL+E0WkB;FF;V{s=4~ZA-#OVsKfZ9rCbep z&HCSHY-pdceXX(HXg(sBkM7}0qL^}+{?Xs_THhq2*d4L$wdU4(W9|9ox9jG!_07to zhwE=~*-c9OgTDQg*@KOzn_CU%DW#hmPy3CnEqklkd_EXFH^cG&?f=bYbB6zKG&i4Z zJzCpb-`IM#)!5wF0RP`;u0MN38V~-z^s8E}zGgn-G-iae)5N2ku*;CMC~^ZoA_E?b z2=xi0k;^z4cnR}ka?W_4dSnprkqIMI1eIkCk~rbavWPnh10F*^KZt3J163-jjWbEj z{A85Uwc)dD9VEUT2Z0x*w=Oshp>xc~tqOrlP{rfW^-oih8X+s>#na+N+yOZp$HTxU z|NZxW$cFjcY={By+L3%Qr=B$(Qcg!lA+z(Q?l@#So=YRyWK`It&BB=MiD}!M2YiAj zi(SUdm(VHp@?o@j^ZAf@=*wEfJ@o_5lq?$mFC_PH!w>$Tq`lQxt5l}oa7g2raUaI! zK^)kDR~K`?r(BWu8Ra-t+9CU1VxdTFPC>gsEfF0{ly<4-(Z0vzx0kLR%TIeT+E~El zx3_Mj+7CiB59-Np-6TxA{_I1${AkmZnkPOcE6W}-`%F$_l(;>oUMApPG{J18;;;d+ zqL`z$Jmy<&WUbX=&|8ZH{SVBJ4f6Cm(g!1y+T{$4+9z)0`VcC7n+f7NfgudAaEHJk zqRAlJ+XPfo*&TutZRgrP2GTk(b6lSGfi$y*TJ^AMkW|(0_uCCnb_XbwCTrEs z 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 ex)MPyhg@H6LC8 diff --git a/archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz b/archive/archive-20220508115432-8f4db45c4efcdff6.tar.gz deleted file mode 100644 index e1e7f930d88b0c726493a97a750e8de04448c445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3767 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 diff --git a/archive/archive-20220508115930-3a2864bd92a8e920.tar.gz b/archive/archive-20220508115930-3a2864bd92a8e920.tar.gz deleted file mode 100644 index b60bea7a5240ba9b3103719c129b080a58387bce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3766 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 diff --git a/justfile b/justfile index 3d29723..2069011 100644 --- a/justfile +++ b/justfile @@ -1,33 +1,21 @@ -set ignore-comments +set shell := ["powershell", "-Command"] +set ignore-comments := true + PACKAGE_SLUG := "src/verbex" -export PYTHON_VERSION := if env("CI","false") != "false" { `python --version|cut -d" " -f2` } else { `cat .python-version` } -PYTHON := if env("USE_SYSTEM_PYTHON", "false") != "false" { "python" } else { ".venv/bin/python" } -PYTHON_ENV := if env("USE_SYSTEM_PYTHON", "false") != "false" { "" } else { "sh .venv/bin/activate &&" } -NEWLINE:="$'\n'" +NEWLINE := "$'\n'" + # print list of commands help: @just --list --unsorted -# install into the venv -install: - @# $(PYTHON_PYENV) - {{if env("CI","false") != "false" { "" } else { "pyenv install --skip-existing $PYTHON_VERSION "} }} - @# $(PYTHON_VENV) - {{ if env("USE_SYSTEM_PYTHON", "false") != "false" { "" } else { "python -m venv .venv" } }} - @# pip - {{PYTHON}} -m pip install -e .[dev,optional,docs] - -# Install pre-commit -pre-commit_install: - pre-commit install # Setup sphynx autodoc setup_autodoc: - sphinx-apidoc -f -o docs/source {{PACKAGE_SLUG}} + sphinx-apidoc -f -o docs/source {{ PACKAGE_SLUG }} # copy as template copy_as_template DEST: - rsync -r --exclude .mypy_cache --exclude .pytest_cache --exclude .ruff_cache --exclude .tox --exclude .venv --exclude *.egg* --exclude .git ./ {{DEST}} - cd {{DEST}} && git init . && git commit --allow-empty -m 'Make initial root commit' + rsync -r --exclude .mypy_cache --exclude .pytest_cache --exclude .ruff_cache --exclude .tox --exclude .venv --exclude *.egg* --exclude .git ./ {{ DEST }} + cd {{ DEST }} && git init . && git commit --allow-empty -m 'Make initial root commit' # profiling profile: @@ -37,35 +25,24 @@ profile: # # Formatting # -# Run all linting and fixes -fixes: validate_pyproject ruff_fixes ruff_format_fixes pylint dapperdata_fixes tomlsort_fixes docs pytest - -_fixes_no_ruff: validate_pyproject dapperdata_fixes tomlsort_fixes docs pytest update_dependencies_quiet # Validate pyproject.toml format validate_pyproject: - {{PYTHON}} -m validate_pyproject pyproject.toml - -# Run pylint -pylint: - {{PYTHON}} -m pylint {{PACKAGE_SLUG}} + uv run validate_pyproject pyproject.toml # Run Ruff and fix ruff_fixes: - {{PYTHON}} -m ruff check . --fix + uv run ruff check . --fix alias black_check := ruff_format_fixes -#Run Ruff format fixes -ruff_format_fixes: - {{PYTHON}} -m ruff format . -# Run dapperdata fixes -dapperdata_fixes: - {{PYTHON}} -m dapperdata.cli pretty . --no-dry-run +# Run Ruff format fixes +ruff_format_fixes: + uv run ruff format . # Run Tomlsort fixes tomlsort_fixes: - {{PYTHON_ENV}} toml-sort `find . -not -path "./.venv/*" -not -path "./.tox/*" -name "*.toml"` -i + $files = Get-ChildItem -Recurse -File -Filter *.toml | Where-Object { $_.FullName -notlike '*\.venv\*' -and $_.FullName -notlike '*\.tox\*' }; foreach ($file in $files) { uv run toml-sort $file.FullName -i } # Generate Docs docs: @@ -74,70 +51,49 @@ docs: # # Testing # + # Run all tests -tests: install pytest ruff_check ruff_format_check mypy dapperdata_check tomlsort_check +tests: pytest ruff_check ruff_format_check # Run Pytest pytest: - {{PYTHON}} -m pytest --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + uv run pytest --cov=./{{ PACKAGE_SLUG }} --cov-report=term-missing tests # Run Pytest verbose pytestvv: - {{PYTHON}} -m pytest -vv --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + uv run pytest -vv --cov=./{{ PACKAGE_SLUG }} --cov-report=term-missing tests # Run pytest show strings pytest_loud: - {{PYTHON}} -m pytest -vv -rA --cov=./{{PACKAGE_SLUG}} --cov-report=term-missing tests + uv run pytest -vv -rA --cov=./{{ PACKAGE_SLUG }} --cov-report=term-missing tests # Run ruff in check mode ruff_check: - {{PYTHON}} -m ruff check + uv run ruff check # Run ruff format in check mode ruff_format_check: - {{PYTHON}} -m ruff format . --check - -# Run mypy check -mypy: - {{PYTHON}} -m mypy {{PACKAGE_SLUG}} + uv run ruff format . --check -# Run dapperdata check -dapperdata_check: - {{PYTHON}} -m dapperdata.cli pretty . +ruff: + just ruff_check + just ruff_format_check # Run tomlsort_check tomlsort_check: - {{PYTHON_ENV}} toml-sort `find . -not -path "./.venv/*" -not -path "./.tox/*" -name "*.toml"` --check + $files = Get-ChildItem -Recurse -File -Filter *.toml | Where-Object { $_.FullName -notlike '*\.venv\*' -and $_.FullName -notlike '*\.tox\*' }; foreach ($file in $files) { uv run toml-sort $file.FullName --check } # # Dependencies # - -# Rebuild dependencies -rebuild_dependencies: - {{PYTHON}} -m uv pip compile --output-file=requirements.txt pyproject.toml - {{PYTHON}} -m uv pip compile --output-file=requirements-dev.txt --extra=dev pyproject.toml - {{PYTHON}} -m uv pip compile --output-file=requirements-optional.txt --extra=optional pyproject.toml - -# Update dependencies -update_dependencies: - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements.txt pyproject.toml - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-dev.txt --extra=dev pyproject.toml - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-optional.txt --extra=optional pyproject.toml - -update_dependencies_quiet: - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements.txt pyproject.toml > /dev/null - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-dev.txt --extra=dev pyproject.toml > /dev/null - {{PYTHON}} -m uv pip compile --upgrade --output-file=requirements-optional.txt --extra=optional pyproject.toml > /dev/null - # # Packaging # # Build package -build: install - {{PYTHON}} -m build +build: + uv run python -m build # Create Git tag for release create_tag tag notes="": - git tag -a v{{tag}} -m "Release {{tag}} "{{NEWLINE}}" {{notes}})" + git tag -a v{{ tag }} -m "Release {{ tag }} "{{ NEWLINE }}" {{ notes }})" diff --git a/pyproject.toml b/pyproject.toml index 27a5520..89f2482 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,45 +3,39 @@ build-backend = "setuptools.build_meta" requires = ["setuptools>=67.0", "wheel"] [project] -authors = [{"name" = "R.Broderick"}] +authors = [{ name = "R.Broderick" }] description = "Python verbal based regular expressions" -version = "2.0.2" -license = {"file" = "LICENSE"} +version = "3.0.0" +license = "GPL-3.0-or-later" +license-files = ["LICENSE"] name = "Verbex" -readme = {file = "README.md", content-type = "text/markdown"} -dependencies = ["beartype", "typing-extensions; python_version < '3.12'"] -requires-python = ">=3.10.0" +readme = { file = "README.md", "content-type" = "text/markdown" } +dependencies = ["beartype", "regex>=2026.4.4"] +requires-python = ">=3.12" classifiers = [ - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.optional-dependencies] dev = [ - "build", - "dapperdata", - "glom", - "mypy", "pytest", "pytest-cov", "pytest-pretty", - "ruamel.yaml", "ruff", "toml-sort", "uv", "validate-pyproject", - "packaging", - "snakeviz", - "pre-commit", "tox", "tox-pyenv-redux", - "pylint", - "perflint", - "snakeviz", - "pip-audit" + "pip-audit", + "pytest-sugar>=1.1.1", + "rust-just>=1.49.0", + "deptry>=0.25.1", + "prek>=0.3.8", + "detect-secrets>=1.5.0", ] optional = [] docs = [ @@ -50,13 +44,32 @@ docs = [ "sphinx-rtd-theme", "sphinx-rtd-size", "autodocsumm", - "sphinx-pyproject" + "sphinx-pyproject", ] [project.urls] homepage = "https://github.com/rbroderi/Verbex" documentation = "https://rbroderi.github.io/Verbex/" +[tool.basedpyright] +venvPath = "." +venv = ".venv" +include = ["src", "tests"] +pythonVersion = "3.12" +reportUnusedCallResult = "none" +reportAny = "none" +reportExplicitAny = "none" +reportImplicitStringConcatenation = "none" +reportUnusedFunction = "none" +reportMissingParameterType = "none" +reportUnknownParameterType = "none" +reportUnknownVariableType = "none" +reportUnknownArgumentType = "none" +reportUnknownMemberType = "none" + +[tool.coverage.run] +omit = ["tests/*"] + [tool.dapperdata] exclude_paths = [".venv", ".mypy_cache", ".git", ".vscode"] @@ -171,7 +184,7 @@ disable = [ "W1509", "W1510", # "W2901", - "W3301" + "W3301", ] [tool.pytest.ini_options] @@ -181,7 +194,7 @@ pythonpath = ["src"] exclude = [".venv"] line-length = 88 indent-width = 4 -target-version = "py310" +target-version = "py312" [tool.ruff.format] # Like Black, use double quotes for strings. @@ -213,14 +226,12 @@ ignore = [ "PIE790", "T201", "PYI013", - "ANN101", - "TCH003", + "TC003", "PLC0414", "ERA001", "T203", - "ANN102", "ANN401", - "TCH002", + "TC002", "TD002", "TD003", "FIX002", @@ -229,7 +240,7 @@ ignore = [ "COM812", "ISC001", "FBT001", - "FBT002" + "FBT002", ] fixable = ["ALL"] unfixable = [] @@ -238,7 +249,7 @@ unfixable = [] force-single-line = true [tool.setuptools.dynamic] -readme = {file = ["README.md"]} +readme = { file = ["README.md"] } [tool.setuptools.package-data] library = ["py.typed"] @@ -258,18 +269,18 @@ extensions = [ "sphinx.ext.coverage", "autodocsumm", "sphinx_rtd_theme", - 'sphinx_rtd_size' + 'sphinx_rtd_size', ] sphinx_rtd_size_width = "90%" templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -auto_doc_default_options = {'autosummary' = true} +auto_doc_default_options = { 'autosummary' = true } # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "sphinx_rtd_theme" html_style = "css/custom.css" html_static_path = ["_static"] -html_theme_options = {'display_version' = true, 'sticky_navigation' = true, 'includehidden' = true, 'titles_only' = false} +html_theme_options = { 'display_version' = true, 'sticky_navigation' = true, 'includehidden' = true, 'titles_only' = false } autosummary_generate = true [tool.sphinx-pyproject.autodoc_default_options] @@ -302,7 +313,7 @@ legacy_tox_ini = """ [tox] skipsdist = True isolated_build = True -envlist = py310, py311, py312 +envlist = py312, py313, py314 [testenv] deps = @@ -312,3 +323,6 @@ deps = commands = pytest tests """ + +[tool.ty.src] +exclude = ["typings"] diff --git a/src/verbex/verbex.py b/src/verbex/verbex.py index 033ab28..80c77a4 100644 --- a/src/verbex/verbex.py +++ b/src/verbex/verbex.py @@ -2,28 +2,19 @@ from __future__ import annotations -import re from collections.abc import Callable -from collections.abc import Iterator +from collections.abc import Iterable from enum import Enum from functools import wraps +from re import RegexFlag from typing import Annotated from typing import Any +from typing import Self from typing import TypeAlias -from typing import Union -from typing import cast -from typing import runtime_checkable - -try: - from typing import Self -except ImportError: - from typing_extensions import Self - -from re import Pattern -from typing import ParamSpec -from typing import Protocol -from typing import TypeVar +from typing import Union # pyright: ignore[reportDeprecated] +from typing import override +import regex from beartype import beartype from beartype.vale import Is @@ -35,42 +26,6 @@ def _string_len_is_1(text: object) -> bool: Char = Annotated[str, Is[_string_len_is_1]] -P = ParamSpec("P") -R = TypeVar("R") - - -# work around for bug https://github.com/python/mypy/issues/12660 -# fixed in next version of mypy. -@runtime_checkable -class HasIter(Protocol): - """Workaround for mypy P.args.""" - - def __iter__(self) -> Iterator[Any]: - """Object can be iterated. - - Yields - ------ - Next object. - - """ - ... - - -# work around for bug https://github.com/python/mypy/issues/12660 -# fixed in next version of mypy -@runtime_checkable -class HasItems(Protocol): - """Workaround for mypy P.kwargs.""" - - def items(self) -> tuple[str, Any]: - """Object has items method. - - :returns: The dict of items. - :rtype: dict - """ - ... - - class EscapedText(str): """Text that has been escaped for regex. @@ -80,7 +35,7 @@ class EscapedText(str): """ - __slots__ = () + __slots__: tuple[()] = () def __new__(cls, value: str) -> Self: """Return an escaped regex string. @@ -90,10 +45,10 @@ def __new__(cls, value: str) -> Self: :rtype: str """ - return str.__new__(cls, re.escape(value)) + return str.__new__(cls, regex.escape(value)) -def re_escape(func: Callable[P, R]) -> Callable[P, R]: +def re_escape[**P, R](func: Callable[P, R]) -> Callable[P, R]: """Automatically escape any string parameters as EscapedText. :param func: The function to decorate. @@ -107,14 +62,14 @@ def re_escape(func: Callable[P, R]) -> Callable[P, R]: def inner(*args: P.args, **kwargs: P.kwargs) -> R: escaped_args: list[Any] = [] escaped_kwargs: dict[str, Any] = {} - for arg in cast(HasIter, args): + for arg in args: if not isinstance(arg, EscapedText) and isinstance(arg, str): escaped_args.append(EscapedText(arg)) else: escaped_args.append(arg) arg_k: str arg_v: Any - for arg_k, arg_v in cast(HasItems, kwargs).items(): + for arg_k, arg_v in kwargs.items(): if not isinstance(arg_v, EscapedText) and isinstance(arg_v, str): escaped_kwargs[arg_k] = EscapedText(str(arg_v)) else: @@ -139,6 +94,7 @@ class CharClass(Enum): WHITESPACE = "\\s" TAB = "\\t" + @override def __str__(self) -> str: """To string method based on Enum value. @@ -158,11 +114,12 @@ class SpecialChar(Enum): """ # does not work / should not be used in [ ] - LINEBREAK = "(\\n|(\\r\\n))" + LINEBREAK = "\\r?\\n" START_OF_LINE = "^" END_OF_LINE = "$" TAB = "\t" + @override def __str__(self) -> str: """To string for special chars enum. @@ -173,12 +130,12 @@ def __str__(self) -> str: return self.value -CharClassOrChars: TypeAlias = str | CharClass -EscapedCharClassOrSpecial: TypeAlias = str | CharClass | SpecialChar -VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] +CharClassOrChars: TypeAlias = str | CharClass # noqa: UP040 +EscapedCharClassOrSpecial: TypeAlias = str | CharClass | SpecialChar # noqa: UP040 +VerbexEscapedCharClassOrSpecial: TypeAlias = Union["Verbex", EscapedCharClassOrSpecial] # noqa: UP040 # pyright: ignore[reportDeprecated] -class Verbex: # pylint: disable=too-many-public-methods +class Verbex: """VerbalExpressions class. The following methods do not try to match the original js lib! @@ -188,15 +145,22 @@ class Verbex: # pylint: disable=too-many-public-methods """ - EMPTY_REGEX_FLAG = re.RegexFlag(0) + EMPTY_REGEX_FLAG: RegexFlag = RegexFlag(0) @re_escape @beartype - def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG) -> None: + def __init__( + self, + modifiers: RegexFlag = EMPTY_REGEX_FLAG, + *, + default_possessive: bool = True, + ) -> None: """Create a Verbex object; setting any needed flags. :param modifiers: Regex modifying flags (default: ``re.RegexFlag(0)``) :type modifiers: re.RegexFlag + :param default_possessive: Default quantifier mode for matching methods. + :type default_possessive: bool :returns: The created Verbex object. :rtype: Verbex @@ -204,10 +168,16 @@ def __init__(self, modifiers: re.RegexFlag = EMPTY_REGEX_FLAG) -> None: """ # self._parts: List[str] = [text] self._parts: list[str] = [] - self._modifiers = modifiers + self._modifiers: RegexFlag = modifiers + self._default_possessive: bool = default_possessive + self._compiled_cache: Any | None = None + + def _invalidate_cache(self) -> None: + """Invalidate compiled regex cache after expression mutation.""" + self._compiled_cache = None @property - def modifiers(self) -> re.RegexFlag: + def modifiers(self) -> RegexFlag: """Return the modifiers for this Verbex object. :return: The modifiers applied to this object. @@ -216,15 +186,6 @@ def modifiers(self) -> re.RegexFlag: """ return self._modifiers - def __str__(self) -> str: - """Return regex string representation. - - :return: The regex string representation. - :rtype: str - - """ - return "".join(self._parts) - @beartype def _add(self, value: str | list[str]) -> Verbex: """Append a transformed value to internal expression to be compiled. @@ -239,21 +200,22 @@ def _add(self, value: str | list[str]) -> Verbex: self._parts.extend(value) else: self._parts.append(value) + self._invalidate_cache() return self - def regex(self) -> Pattern[str]: + def regex(self) -> Any: """Get a regular expression object. :return: A regular expression object. - :rtype: Pattern[str] + :rtype: Any """ - return re.compile( - str(self), - self._modifiers, - ) - - # allow VerbexEscapedCharClassOrSpecial + if self._compiled_cache is None: + self._compiled_cache = regex.compile( + str(self), + self._modifiers, + ) + return self._compiled_cache @re_escape @beartype @@ -308,6 +270,10 @@ def capture_group( def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 """`or` is a python keyword so we use `OR` instead. + This alternates only the most recently appended fragment, not the + entire expression. Use ``or_expr`` to alternate against the full + accumulated expression. + :param text: Text to find or a Verbex object. :type text: VerbexEscapedCharClassOrSpecial @@ -315,35 +281,188 @@ def OR(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: # noqa: N802 :rtype: Verbex """ - return self._add("|").find(text) + if not self._parts: + return self.find(text) + + left = self._parts.pop() + right = str(text) + self._parts.append(f"(?:{left}|{right})") + self._invalidate_cache() + return self + + @re_escape + @beartype + def or_expr(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Alternate against the entire expression built so far.""" + if not self._parts: + return self.find(text) + + left = str(self) + right = str(text) + self._parts = [f"(?:{left}|{right})"] + self._invalidate_cache() + return self + + @staticmethod + def _range_same_width(start: str, end: str) -> list[str]: + """Build compact regex parts for an inclusive numeric range with equal width.""" + _max_single_digit = 9 + + if start == end: + return [start] + + if len(start) == 1: + s = int(start) + e = int(end) + if s == 0 and e == _max_single_digit: + return [r"\d"] + return [f"[{s}-{e}]"] + + first_s = int(start[0]) + first_e = int(end[0]) + tail_len = len(start) - 1 + full_tail = r"\d" if tail_len == 1 else rf"\d{{{tail_len}}}" + + parts: list[str] = [] + for digit in range(first_s, first_e + 1): + low_tail = start[1:] if digit == first_s else "0" * tail_len + high_tail = end[1:] if digit == first_e else "9" * tail_len + + if low_tail == "0" * tail_len and high_tail == "9" * tail_len: + parts.append(f"{digit}{full_tail}") + continue + + tails = Verbex._range_same_width(low_tail, high_tail) + parts.extend(f"{digit}{tail}" for tail in tails) + + return parts + + @staticmethod + def _int_range_to_regex_parts(start: int, end: int) -> list[str]: + """Build compact regex parts for an inclusive non-negative integer range.""" + if start < 0 or end < 0: + return [str(i) for i in range(start, end + 1)] + + parts: list[str] = [] + start_width = len(str(start)) + end_width = len(str(end)) + for width in range(start_width, end_width + 1): + low = start if width == start_width else 10 ** (width - 1) + high = end if width == end_width else (10**width) - 1 + start_text = str(low).zfill(width) + end_text = str(high).zfill(width) + parts.extend(Verbex._range_same_width(start_text, end_text)) + return parts + + @staticmethod + def _optimize_alternation(parts: Iterable[str]) -> str: + """Join alternation parts while removing obvious duplicates.""" + deduped = list(dict.fromkeys(parts)) + if len(deduped) == 1: + return deduped[0] + return "(?:" + "|".join(deduped) + ")" + + @staticmethod + def _is_atomic(text: str) -> bool: + """Check if text needs no grouping for a quantifier.""" + _escape_seq_len = 2 + if len(text) <= 1: + return True + if len(text) == _escape_seq_len and text[0] == "\\": + return True + if text[0] == "[" and text[-1] == "]" and "[" not in text[1:]: + return True + if text[0] == "(" and text[-1] == ")": + depth = 0 + for i, ch in enumerate(text): + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + if depth == 0 and i < len(text) - 1: + return False + return True + return False + + def _repeat( + self, + text: VerbexEscapedCharClassOrSpecial, + quantifier: str, + ) -> Verbex: + """Apply a regex quantifier to a grouped text fragment.""" + s = str(text) + if self._is_atomic(s): + return self._add(f"{s}{quantifier}") + return self._add(f"(?:{s}){quantifier}") + + def _resolve_possessive(self, possessive: bool | None) -> bool: + """Resolve effective possessive mode from override or object default.""" + if possessive is None: + return self._default_possessive + return possessive + + def _maybe_possessive(self, quantifier: str, possessive: bool | None) -> str: + """Return quantifier with optional possessive suffix.""" + return f"{quantifier}+" if self._resolve_possessive(possessive) else quantifier @re_escape @beartype - def zero_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + def atomic(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + """Wrap text in an atomic group to reduce backtracking.""" + s = str(text) + if self._is_atomic(s): + return self._add(f"(?>{s})") + return self._add(f"(?>(?:{s}))") + + @re_escape + @beartype + def zero_or_more( + self, + text: VerbexEscapedCharClassOrSpecial, + *, + possessive: bool | None = None, + ) -> Verbex: """Find the text or Verbex object zero or more times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to look for. :type text: VerbexEscapedCharClassOrSpecial + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s})*") + return self._repeat(text, self._maybe_possessive("*", possessive)) @re_escape @beartype - def one_or_more(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + def one_or_more( + self, + text: VerbexEscapedCharClassOrSpecial, + *, + possessive: bool | None = None, + ) -> Verbex: """Find the text or Verbex object one or more times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to look for. :type text: VerbexEscapedCharClassOrSpecial + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s})+") + return self._repeat(text, self._maybe_possessive("+", possessive)) @re_escape @beartype @@ -351,16 +470,24 @@ def n_times( self, text: VerbexEscapedCharClassOrSpecial, n: int, + *, + possessive: bool | None = None, ) -> Verbex: """Find the text or Verbex object n or more times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to look for. :type text: VerbexEscapedCharClassOrSpecial + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s}){{{n}}}") + return self._repeat(text, self._maybe_possessive(f"{{{n}}}", possessive)) @re_escape @beartype @@ -368,16 +495,24 @@ def n_times_or_more( self, text: VerbexEscapedCharClassOrSpecial, n: int, + *, + possessive: bool | None = None, ) -> Verbex: """Find the text or Verbex object at least n times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to look for. :type text: VerbexEscapedCharClassOrSpecial + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s}){{{n},}}") + return self._repeat(text, self._maybe_possessive(f"{{{n},}}", possessive)) @re_escape @beartype @@ -386,34 +521,53 @@ def n_to_m_times( text: VerbexEscapedCharClassOrSpecial, n: int, m: int, + *, + possessive: bool | None = None, ) -> Verbex: """Find the text or Verbex object between n and m times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to look for. :type text: VerbexEscapedCharClassOrSpecial :param n: The minimum number of times to find the text. :type n: int :param m: The maximum number of times to find the text. :type m: int + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s}){{{n},{m}}}") + return self._repeat(text, self._maybe_possessive(f"{{{n},{m}}}", possessive)) @re_escape @beartype - def maybe(self, text: VerbexEscapedCharClassOrSpecial) -> Verbex: + def maybe( + self, + text: VerbexEscapedCharClassOrSpecial, + *, + possessive: bool | None = None, + ) -> Verbex: """Possibly find the text / Verbex object. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + :param text: The text / Verbex object to possibly find. :type text: VerbexEscapedCharClassOrSpecial + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool :return: Modified Verbex object. :rtype: Verbex """ - return self._add(f"(?:{text!s})?") + return self._repeat(text, self._maybe_possessive("?", possessive)) @re_escape @beartype @@ -507,7 +661,7 @@ def any_of(self, chargroup: CharClassOrChars) -> Verbex: :rtype: Verbex """ - return self._add(f"(?:[{chargroup}])") + return self._add(f"[{chargroup}]") @re_escape @beartype @@ -521,7 +675,7 @@ def not_any_of(self, text: CharClassOrChars) -> Verbex: :rtype: Verbex """ - return self._add(f"(?:[^{text}])") + return self._add(f"[^{text}]") @re_escape def anything_but(self, chargroup: EscapedCharClassOrSpecial) -> Verbex: @@ -574,14 +728,29 @@ def tab(self) -> Verbex: """ return self.find(SpecialChar.TAB) - def anything(self) -> Verbex: + def anything(self, *, possessive: bool | None = None) -> Verbex: """Find anything one or more times. + .. note:: + Possessive quantification is enabled by default for faster evaluation. + Set ``possessive=False`` to restore traditional backtracking behavior. + + :param possessive: Override object default quantifier mode when provided. + :type possessive: bool + :return: Modified Verbex object. :rtype: Verbex """ - return self._add(".+") + quantifier = self._maybe_possessive("+", possessive) + return self._add(f".{quantifier}") + + def anything_lazy(self) -> Verbex: + """Find anything lazily, allowing shortest match first. + + Equivalent to ``anything(possessive=False).as_few()``. + """ + return self.anything(possessive=False).as_few() def as_few(self) -> Verbex: """Modify previous search to not be greedy. @@ -604,7 +773,15 @@ def number_range(self, start: int, end: int) -> Verbex: :rtype: Verbex """ - return self._add("(?:" + "|".join(str(i) for i in range(start, end + 1)) + ")") + if start > end: + msg = "start must be less than or equal to end" + raise ValueError(msg) + + if start == end: + return self._add(str(start)) + + parts = self._int_range_to_regex_parts(start, end) + return self._add(self._optimize_alternation(parts)) @beartype def letter_range(self, start: Char, end: Char) -> Verbex: @@ -627,7 +804,13 @@ def word(self) -> Verbex: :rtype: Verbex """ - return self._add("(\\b\\w+\\b)") + return self._add("(?:\\b\\w+\\b)") + + def _set_modifier(self, flag: RegexFlag) -> Verbex: + """Enable a regex flag on the current expression.""" + self._modifiers |= flag + self._invalidate_cache() + return self # # --------------- modifiers ------------------------ @@ -638,8 +821,7 @@ def with_any_case(self) -> Verbex: :rtype: Verbex """ - self._modifiers |= re.IGNORECASE - return self + return self._set_modifier(RegexFlag.IGNORECASE) def search_by_line(self) -> Verbex: """Search each line, ^ and $ match beginning and end of line respectively. @@ -648,8 +830,7 @@ def search_by_line(self) -> Verbex: :rtype: Verbex """ - self._modifiers |= re.MULTILINE - return self + return self._set_modifier(RegexFlag.MULTILINE) def with_ascii(self) -> Verbex: """Match ascii instead of unicode. @@ -658,19 +839,14 @@ def with_ascii(self) -> Verbex: :rtype: Verbex """ - self._modifiers |= re.ASCII - return self - - -# left over notes from original version -# def __getattr__(self, attr): -# """ any other function will be sent to the regex object """ -# regex = self.regex() -# return getattr(regex, attr) + return self._set_modifier(RegexFlag.ASCII) -# def replace(self, string, repl): -# return self.sub(repl, string) + @override + def __str__(self) -> str: + """Return regex string representation. + :return: The regex string representation. + :rtype: str -if __name__ == "__main__": - pass + """ + return "".join(self._parts) diff --git a/tests/test_verbex.py b/tests/test_verbex.py index ca27a72..f380ae7 100644 --- a/tests/test_verbex.py +++ b/tests/test_verbex.py @@ -1,306 +1,531 @@ +"""Tests for Verbex behavior.""" + # pyright: reportPrivateUsage=false -# flake8: noqa -# type: ignore -# pylint: disable-all -import re -import unittest - -from verbex import CharClass, SpecialChar, Verbex - - -class verbexTest(unittest.TestCase): - """Tests for verbal_expressions.py""" - - # def setUp(self): - # Verbex() = Verbex() - - # def tearDown(self): - # ... - # # Verbex() = None - # # self.exp = None - - def test_should_render_verbex_as_string(self): - self.assertEqual(str(Verbex()._add("^$")), "^$") - - def test_should_render_verbex_list_as_string(self): - self.assertEqual(str(Verbex()._add(["^", "[0-9]", "$"])), "^[0-9]$") - - def test_should_match_characters_in_range(self): - regex = Verbex().letter_range("a", "c").regex() - for character in ["a", "b", "c"]: - self.assertRegex(character, regex) - - def test_should_not_match_characters_outside_of_range(self): - regex = Verbex().letter_range("a", "c").regex() - self.assertNotRegex("d", regex) - - def test_should_match_start_of_line(self): - regex = Verbex().find(SpecialChar.START_OF_LINE).find("text ").regex() - self.assertRegex("text ", regex) - - def test_should_match_end_of_line(self): - regex = Verbex().find("test").find(SpecialChar.END_OF_LINE).regex() - self.assertRegex("IGNORE test", regex) - - def test_should_match_anything(self): - regex = Verbex().anything().regex() - self.assertIsNotNone(re.fullmatch(regex, "!@#$%¨&*()__+{}")) - - def test_should_match_anything_but_specified_element_when_element_is_not_found( - self, - ): - regex = Verbex().anything_but("X").find(" Files").regex() - self.assertRegex("Y Files", regex) - self.assertNotRegex("X Files", regex) - - def test_should_not_match_anything_but_specified_element_when_specified_element_is_found( - self, - ): - regex = Verbex().anything_but("X").regex() - self.assertRegex("Y Files", regex) - self.assertNotRegex("X", regex) - - def test_should_find_element(self): - regex = Verbex().find("Wally").regex() - self.assertRegex("Wally", regex) - self.assertNotRegex("Nally", regex) - - def test_should_not_find_missing_element(self): - regex = Verbex().find("Wally").regex() - self.assertNotRegex("Wall-e", regex) - - def test_should_match_when_maybe_element_is_present(self): - regex = ( - Verbex() - .start_of_line() - .find("Python2.") - .maybe("7") - .end_of_line() - .regex() # - ) - self.assertRegex("Python2.7", regex) - - def test_should_match_when_maybe_element_is_missing(self): - regex = ( - Verbex() - .start_of_line() - .find("Python2.") - .maybe("7") - .end_of_line() - .regex() # - ) - self.assertRegex("Python2.", regex) - - def test_should_match_on_any_when_element_is_found(self): - regex = ( - Verbex() - .start_of_line() - .any_of("Q") - .anything() - .end_of_line() - .regex() # E501 # - ) - self.assertRegex("Query", regex) - - def test_should_not_match_on_any_when_element_is_not_found(self): - regex = ( - Verbex() - .start_of_line() - .any_of("Q") - .anything() - .end_of_line() - .regex() # E501 # - ) - self.assertNotRegex("W", regex) - - def test_should_match_when_line_break_present(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - self.assertRegex("Marco \n Polo", regex) - self.assertNotRegex("Marco Polo", regex) - - def test_should_match_when_line_break_and_carriage_return_present(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - self.assertRegex("Marco \r\n Polo", regex) - - def test_should_not_match_when_line_break_is_missing(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .line_break() - .anything() - .end_of_line() - .regex() - ) - self.assertNotRegex("Marco Polo", regex) - - def test_should_match_when_tab_present(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .as_few() - .find("!") - .tab() - .end_of_line() - .regex() # E501 # - ) - self.assertRegex("One tab only!\t", regex) - self.assertNotRegex("One tab only!\t\t", regex) - - def test_should_not_match_when_tab_is_missing(self): - regex = Verbex().start_of_line().anything().tab().end_of_line().regex() - self.assertNotRegex("No tab here", regex) - - def test_should_match_when_word_present(self): - regex = Verbex().start_of_line().word().end_of_line().regex() - self.assertRegex("Oneword", regex) - - def test_not_match_when_two_words_are_present_instead_of_one(self): - regex = Verbex().start_of_line().word().end_of_line().regex() - self.assertNotRegex("Two words", regex) - - def test_should_match_when_or_condition_fulfilled(self): - regex = ( - Verbex() - .start_of_line() - .find("G") - .OR(Verbex().find("H")) - .anything() - .as_few() - .find("b") - .end_of_line() - .regex() - ) - self.assertRegex("Github", regex) - self.assertRegex("Hithub", regex) - - def test_should_not_match_when_or_condition_not_fulfilled(self): - regex = ( - Verbex() - .start_of_line() - .find("G") - .OR(Verbex().find("H")) - .anything() - .as_few() - .find("b") - .end_of_line() - .regex() - ) - self.assertNotRegex("ithub", regex) - - def test_should_match_on_upper_case_when_lower_case_is_given_and_any_case( - self, - ): - regex = ( - Verbex() - .start_of_line() - .find("THOR") - .end_of_line() - .with_any_case() - .regex() # E501 # - ) - self.assertRegex("thor", regex) - - def test_should_not_match_on_upper_case_when_lower_case_is_given( - self, - ): - regex = Verbex().start_of_line().find("THOR").end_of_line().regex() - self.assertNotRegex("thor", regex) - - def test_should_match_multiple_lines(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .find("Pong") - .anything() - .end_of_line() - .search_by_line() - .regex() - ) - self.assertRegex("Ping \n Pong \n Ping", regex) - - def test_should_not_match_multiple_lines(self): - regex = ( - Verbex() - .start_of_line() - .anything() - .find("Pong") - .anything() - .end_of_line() - .regex() - ) - self.assertNotRegex("Ping \n Pong \n Ping", regex) - - def test_should_match_email_like(self): - regex = ( - Verbex() - .start_of_line() - .one_or_more(Verbex().any_of(CharClass.LETTER)) - .then("@") - .one_or_more(Verbex().any_of(CharClass.LETTER)) - .then(".") - .one_or_more(Verbex().any_of(CharClass.LETTER)) - .end_of_line() - .regex() - ) - self.assertRegex("mail@mail.com", regex) - - def test_should_match_url(self): - regex = ( - Verbex() - .start_of_line() - .then("http") - .maybe("s") - .then("://") - .maybe("www.") - .word() - .then(".") - .word() - .maybe("/") - .end_of_line() - .regex() - ) - self.assertRegex("https://www.google.com/", regex) - self.assertNotRegex("htps://www.google.com/", regex) - - def test_followed_by(self): - regex = Verbex().find("!").followed_by(":").regex() - self.assertRegex("!:", regex) - self.assertNotRegex("! :", regex) - - def test_not_followed_by(self): - regex = Verbex().find("!").not_followed_by(":").regex() - self.assertNotRegex("!:", regex) - self.assertRegex("! :", regex) - - def test_preceded_by(self): - regex = Verbex().preceded_by("!").find(":").regex() - self.assertRegex("!:", regex) - self.assertNotRegex("! :", regex) - - def test_not_preceded_by(self): - regex = Verbex().not_preceded_by("!").find(":").regex() - self.assertNotRegex("!:", regex) - self.assertRegex("! :", regex) - - -if __name__ == "__main__": - unittest.main() +# ruff: noqa: S101 +from re import RegexFlag + +import pytest + +from verbex import CharClass +from verbex import SpecialChar +from verbex import Verbex + + +def test_should_render_verbex_as_string() -> None: + """Test case.""" + v = Verbex().start_of_line().end_of_line() + assert str(v) == "^$" + assert v.regex() + + +def test_regex_compilation_is_cached_and_invalidated() -> None: + """Test case.""" + expr = Verbex().find("abc") + compiled1 = expr.regex() + compiled2 = expr.regex() + assert compiled1 is compiled2 + + expr.find("def") + compiled3 = expr.regex() + assert compiled3 is not compiled1 + + +def test_should_render_verbex_list_as_string() -> None: + """Test case.""" + v = Verbex().start_of_line().any_of("0-9").end_of_line() + assert str(v) == "^[0\\-9]$" + assert v.regex() + + +def test_should_match_characters_in_range() -> None: + """Test case.""" + regex = Verbex().letter_range("a", "c").regex() + for character in ["a", "b", "c"]: + assert regex.search(character) is not None + + +def test_should_not_match_characters_outside_of_range() -> None: + """Test case.""" + regex = Verbex().letter_range("a", "c").regex() + assert regex.search("d") is None + + +def test_should_match_start_of_line() -> None: + """Test case.""" + regex = Verbex().find(SpecialChar.START_OF_LINE).find("text ").regex() + assert regex.search("text ") is not None + + +def test_should_match_end_of_line() -> None: + """Test case.""" + regex = Verbex().find("test").find(SpecialChar.END_OF_LINE).regex() + assert regex.search("IGNORE test") is not None + + +def test_should_match_anything() -> None: + """Test case.""" + regex = Verbex().anything().regex() + assert regex.fullmatch("!@#$%¨&*()__+{}") is not None + + +def test_should_match_anything_but_specified_element_when_not_found() -> None: + """Test case.""" + regex = Verbex().anything_but("X").find(" Files").regex() + assert regex.search("Y Files") is not None + assert regex.search("X Files") is None + + +def test_should_not_match_anything_but_specified_element_when_found() -> None: + """Test case.""" + regex = Verbex().anything_but("X").regex() + assert regex.search("Y Files") is not None + assert regex.search("X") is None + + +def test_should_find_element() -> None: + """Test case.""" + regex = Verbex().find("Wally").regex() + assert regex.search("Wally") is not None + assert regex.search("Nally") is None + + +def test_should_not_find_missing_element() -> None: + """Test case.""" + regex = Verbex().find("Wally").regex() + assert regex.search("Wall-e") is None + + +def test_should_match_when_maybe_element_is_present() -> None: + """Test case.""" + regex = Verbex().start_of_line().find("Python2.").maybe("7").end_of_line().regex() + assert regex.search("Python2.7") is not None + + +def test_should_match_when_maybe_element_is_missing() -> None: + """Test case.""" + regex = Verbex().start_of_line().find("Python2.").maybe("7").end_of_line().regex() + assert regex.search("Python2.") is not None + + +def test_should_match_on_any_when_element_is_found() -> None: + """Test case.""" + regex = Verbex().start_of_line().any_of("Q").anything().end_of_line().regex() + assert regex.search("Query") is not None + + +def test_should_not_match_on_any_when_element_is_not_found() -> None: + """Test case.""" + regex = Verbex().start_of_line().any_of("Q").anything().end_of_line().regex() + assert regex.search("W") is None + + +def test_should_match_when_line_break_present() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .line_break() + .anything(possessive=False) + .end_of_line() + .regex() + ) + assert regex.search("Marco \n Polo") is not None + assert regex.search("Marco Polo") is None + + +def test_should_match_when_line_break_and_carriage_return_present() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .line_break() + .anything(possessive=False) + .end_of_line() + .regex() + ) + assert regex.search("Marco \r\n Polo") is not None + + +def test_should_not_match_when_line_break_is_missing() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .line_break() + .anything(possessive=False) + .end_of_line() + .regex() + ) + assert regex.search("Marco Polo") is None + + +def test_should_match_when_tab_present() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .as_few() + .find("!") + .tab() + .end_of_line() + .regex() + ) + assert regex.search("One tab only!\t") is not None + assert regex.search("One tab only!\t\t") is None + + +def test_should_not_match_when_tab_is_missing() -> None: + """Test case.""" + regex = ( + Verbex().start_of_line().anything(possessive=False).tab().end_of_line().regex() + ) + assert regex.search("No tab here") is None + + +def test_should_match_when_word_present() -> None: + """Test case.""" + regex = Verbex().start_of_line().word().end_of_line().regex() + assert regex.search("Oneword") is not None + + +def test_not_match_when_two_words_are_present_instead_of_one() -> None: + """Test case.""" + regex = Verbex().start_of_line().word().end_of_line().regex() + assert regex.search("Two words") is None + + +def test_should_match_when_or_condition_fulfilled() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .find("G") + .OR(Verbex().find("H")) + .anything(possessive=False) + .as_few() + .find("b") + .end_of_line() + .regex() + ) + assert regex.search("Github") is not None + assert regex.search("Hithub") is not None + + +def test_should_not_match_when_or_condition_not_fulfilled() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .find("G") + .OR(Verbex().find("H")) + .anything(possessive=False) + .as_few() + .find("b") + .end_of_line() + .regex() + ) + assert regex.search("ithub") is None + + +def test_should_match_on_upper_case_when_lower_case_is_given_and_any_case() -> None: + """Test case.""" + regex = Verbex().start_of_line().find("THOR").end_of_line().with_any_case().regex() + assert regex.search("thor") is not None + + +def test_should_not_match_on_upper_case_when_lower_case_is_given() -> None: + """Test case.""" + regex = Verbex().start_of_line().find("THOR").end_of_line().regex() + assert regex.search("thor") is None + + +def test_should_match_multiple_lines() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .find("Pong") + .anything(possessive=False) + .end_of_line() + .search_by_line() + .regex() + ) + assert regex.search("Ping \n Pong \n Ping") is not None + + +def test_should_not_match_multiple_lines() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .anything(possessive=False) + .find("Pong") + .anything(possessive=False) + .end_of_line() + .regex() + ) + assert regex.search("Ping \n Pong \n Ping") is None + + +def test_should_match_email_like() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .then("@") + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .then(".") + .one_or_more(Verbex().any_of(CharClass.LETTER)) + .end_of_line() + .regex() + ) + assert regex.search("mail@mail.com") is not None + + +def test_should_match_url() -> None: + """Test case.""" + regex = ( + Verbex() + .start_of_line() + .then("http") + .maybe("s") + .then("://") + .maybe("www.") + .word() + .then(".") + .word() + .maybe("/") + .end_of_line() + .regex() + ) + assert regex.search("https://www.google.com/") is not None + assert regex.search("htps://www.google.com/") is None + + +def test_followed_by() -> None: + """Test case.""" + regex = Verbex().find("!").followed_by(":").regex() + assert regex.search("!:") is not None + assert regex.search("! :") is None + + +def test_not_followed_by() -> None: + """Test case.""" + regex = Verbex().find("!").not_followed_by(":").regex() + assert regex.search("!:") is None + assert regex.search("! :") is not None + + +def test_preceded_by() -> None: + """Test case.""" + regex = Verbex().preceded_by("!").find(":").regex() + assert regex.search("!:") is not None + assert regex.search("! :") is None + + +def test_not_preceded_by() -> None: + """Test case.""" + regex = Verbex().not_preceded_by("!").find(":").regex() + assert regex.search("!:") is None + assert regex.search("! :") is not None + + +def test_modifiers_property_and_with_ascii() -> None: + """Test case.""" + expr = Verbex() + assert expr.modifiers == RegexFlag(0) + expr.with_ascii() + assert expr.modifiers & RegexFlag.ASCII + assert expr.regex() + + +def test_private_add_list_branch_via_getattr() -> None: + """Test case.""" + expr = Verbex() + add = expr._add # noqa: SLF001 + add(["^", "abc", "$"]) + assert str(expr) == "^abc$" + assert expr.regex() + + +def test_capture_group_unnamed_named_and_error() -> None: + """Test case.""" + unnamed = Verbex().capture_group("abc").regex() + assert unnamed.search("abc") is not None + + named_expr = Verbex().capture_group(name_or_text="group", text="abc") + assert str(named_expr) == "(?abc)" + assert named_expr.regex().search("abc") is not None + + named_non_string_expr = Verbex().capture_group( + name_or_text="tab_group", + text=SpecialChar.TAB, + ) + assert str(named_non_string_expr) == "(?\t)" + assert named_non_string_expr.regex().search("\t") is not None + + with pytest.raises(ValueError, match="text must be specified"): + Verbex().capture_group(None) + + +def test_quantifier_methods() -> None: + """Test case.""" + assert Verbex().start_of_line().zero_or_more("ab").end_of_line().regex().search("") + assert Verbex().start_of_line().n_times("a", 2).end_of_line().regex().search("aa") + assert ( + Verbex() + .start_of_line() + .n_times_or_more("a", 2) + .end_of_line() + .regex() + .search("aaa") + ) + assert ( + Verbex() + .start_of_line() + .n_to_m_times("a", 2, 3) + .end_of_line() + .regex() + .search("aaa") + ) + + +def test_not_any_of_and_number_range() -> None: + """Test case.""" + not_any = Verbex().start_of_line().not_any_of("abc").end_of_line().regex() + assert not_any.search("d") is not None + assert not_any.search("a") is None + + number_range = Verbex().start_of_line().number_range(10, 12).end_of_line().regex() + assert number_range.search("10") is not None + assert number_range.search("11") is not None + assert number_range.search("12") is not None + assert number_range.search("13") is None + + +def test_quantifier_skips_group_for_atomic_patterns() -> None: + """Test case.""" + # Single char - no group needed + assert str(Verbex().maybe("x")) == "x?+" + # Escape sequence - no group needed + assert str(Verbex().one_or_more(CharClass.DIGIT)) == "\\d++" + # Character class - no group needed + v = Verbex().zero_or_more(Verbex().any_of("abc")) + assert str(v) == "[abc]*+" + assert v.regex() + # Capture group - no extra wrapping + v2 = Verbex().zero_or_more(Verbex().capture_group("a")) + assert str(v2) == "(a)*+" + assert v2.regex() + # Adjacent groups - needs wrapping + v3 = Verbex().zero_or_more( + Verbex().capture_group("a").capture_group("b"), + ) + assert str(v3) == "(?:(a)(b))*+" + assert v3.regex() + # Multi-char text - needs wrapping + assert str(Verbex().maybe("ab")) == "(?:ab)?+" + + +def test_or_is_scoped_to_previous_fragment() -> None: + """Test case.""" + regex = Verbex().start_of_line().find("G").OR("H").end_of_line().regex() + assert regex.search("G") is not None + assert regex.search("H") is not None + assert regex.search("xH") is None + + +def test_atomic_and_possessive_methods_compile() -> None: + """Test case.""" + assert str(Verbex().anything_lazy()) == ".+?" + assert str(Verbex().anything()) == ".++" + assert str(Verbex().anything(possessive=False)) == ".+" + assert str(Verbex().atomic("abc")) == "(?>(?:abc))" + assert str(Verbex().atomic(CharClass.DIGIT)) == "(?>\\d)" + + z = Verbex().start_of_line().zero_or_more("a").end_of_line() + assert str(z) == "^a*+$" + assert z.regex().search("aaaa") is not None + + o = Verbex().start_of_line().one_or_more("a").end_of_line() + assert str(o) == "^a++$" + assert o.regex().search("aaaa") is not None + + m = Verbex().start_of_line().maybe("a").end_of_line() + assert str(m) == "^a?+$" + assert m.regex().search("") is not None + assert m.regex().search("a") is not None + + +def test_number_range_uses_compact_pattern_and_validates_bounds() -> None: + """Test case.""" + compact = str(Verbex().number_range(100, 199)) + assert "\\d" in compact + + regex_100_199 = ( + Verbex().start_of_line().number_range(100, 199).end_of_line().regex() + ) + assert regex_100_199.search("100") is not None + assert regex_100_199.search("150") is not None + assert regex_100_199.search("199") is not None + assert regex_100_199.search("99") is None + assert regex_100_199.search("200") is None + + with pytest.raises(ValueError, match="start must be less than or equal to end"): + Verbex().number_range(3, 2) + + +def test_or_and_number_range_edge_cases() -> None: + """Test case.""" + # OR with no left-hand fragment should behave like find(). + assert str(Verbex().OR("x")) == "x" + assert Verbex().OR("x").regex().search("x") is not None + + # Single-value range is direct and compact. + assert str(Verbex().number_range(5, 5)) == "5" + + # Compact single-digit ranges. + assert str(Verbex().number_range(0, 9)) == "\\d" + assert str(Verbex().number_range(2, 4)) == "[2-4]" + + # Negative ranges are still supported via explicit alternation fallback. + neg = Verbex().start_of_line().number_range(-1, 1).end_of_line().regex() + assert neg.search("-1") is not None + assert neg.search("0") is not None + assert neg.search("1") is not None + assert neg.search("2") is None + + +def test_or_expr_alternates_against_whole_expression() -> None: + """Test case.""" + regex = Verbex().find("ab").find("cd").or_expr("xy").end_of_line().regex() + assert regex.search("abcd") is not None + assert regex.search("xy") is not None + assert regex.search("cd") is None + + +def test_or_expr_when_expression_is_empty() -> None: + """Test case.""" + v = Verbex().or_expr("x") + assert str(v) == "x" + assert v.regex().search("x") is not None + + +def test_default_possessive_can_be_overridden_per_instance() -> None: + """Test case.""" + non_pos = Verbex(default_possessive=False) + assert str(non_pos.one_or_more("a")) == "a+" + assert ( + str(Verbex(default_possessive=False).one_or_more("a", possessive=True)) == "a++" + ) + + +def test_range_same_width_equal_values_branch() -> None: + """Test case.""" + assert Verbex._range_same_width("7", "7") == ["7"] # noqa: SLF001 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..5cc26a8 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1469 @@ +version = 1 +revision = 1 +requires-python = ">=3.12" + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, +] + +[[package]] +name = "autodocsumm" +version = "0.2.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/f28dea12fae1d1ad1e706f5cf6d16e8d735f305ebee86fd9390e099bd27d/autodocsumm-0.2.15.tar.gz", hash = "sha256:eaf431e7a5a39e41a215311173c8b95e83859059df1ccf3b79c64bf3d5582b3c", size = 46674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/3d/4357a0f685c0a2ae7132ac91905bec565e64f9ba63b079f7ec5da46e3597/autodocsumm-0.2.15-py3-none-any.whl", hash = "sha256:dbe6fabcaeae4540748ea9b3443eb76c2692e063d44f004f67c424610a5aca9a", size = 14852 }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658 }, +] + +[[package]] +name = "boolean-py" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577 }, +] + +[[package]] +name = "cachecontrol" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247 }, +] + +[package.optional-dependencies] +filecache = [ + { name = "filelock" }, +] + +[[package]] +name = "cachetools" +version = "7.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918 }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328 }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061 }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031 }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239 }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589 }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733 }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652 }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229 }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552 }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806 }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316 }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274 }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468 }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460 }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330 }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828 }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627 }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008 }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303 }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282 }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595 }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986 }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711 }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036 }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998 }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056 }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537 }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176 }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723 }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085 }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819 }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915 }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234 }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042 }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706 }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727 }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882 }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860 }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564 }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276 }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238 }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189 }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352 }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024 }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869 }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541 }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634 }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384 }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133 }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257 }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851 }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393 }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609 }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014 }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979 }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238 }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110 }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824 }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103 }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194 }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827 }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168 }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018 }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958 }, +] + +[[package]] +name = "click" +version = "8.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554 }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908 }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419 }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159 }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270 }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538 }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821 }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191 }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337 }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404 }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903 }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780 }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093 }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900 }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515 }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576 }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942 }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935 }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541 }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780 }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912 }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165 }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908 }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873 }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030 }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694 }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469 }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112 }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923 }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540 }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262 }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617 }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912 }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987 }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416 }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558 }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163 }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981 }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604 }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321 }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502 }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688 }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788 }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851 }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104 }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621 }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953 }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992 }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503 }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852 }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161 }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021 }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858 }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823 }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099 }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638 }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295 }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360 }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174 }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739 }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351 }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612 }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985 }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107 }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513 }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650 }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089 }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982 }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579 }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316 }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427 }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745 }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146 }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254 }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276 }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346 }, +] + +[[package]] +name = "cyclonedx-python-lib" +version = "11.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "license-expression" }, + { name = "packageurl-python" }, + { name = "py-serializable" }, + { name = "sortedcontainers" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/0d/64f02d3fd9c116d6f50a540d04d1e4f2e3c487f5062d2db53733ddb25917/cyclonedx_python_lib-11.7.0.tar.gz", hash = "sha256:fb1bc3dedfa31208444dbd743007f478ab6984010a184e5bd466bffd969e936e", size = 1411174 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/09/fe0e3bc32bd33707c519b102fc064ad2a2ce5a1b53e2be38b86936b476b1/cyclonedx_python_lib-11.7.0-py3-none-any.whl", hash = "sha256:02fa4f15ddbba21ac9093039f8137c0d1813af7fe88b760c5dcd3311a8da2178", size = 513041 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "deptry" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "packaging" }, + { name = "requirements-parser" }, + { name = "tomli", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/b2/50ccc99362ae7757342978b7ecb3b98e47fade721fd617d74db1948ec3a1/deptry-0.25.1.tar.gz", hash = "sha256:45c8cd982c85cd4faae573ddff6920de7eec735336db6973f26a765ae7950f7d", size = 509748 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/1d/b538dc635e873b25360d761cfe1fa0ccd7d6c69b698047e552f33401e60d/deptry-0.25.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a4dd1148db24a1ddacfa8b840836c6019c2f864fcb7579dd089fd217606338c8", size = 1850319 }, + { url = "https://files.pythonhosted.org/packages/fe/a9/511477a8f0ae4f6021d68a80bdca77e7ffb0722008dc24ee5d9ef49f5c88/deptry-0.25.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c67c666d916ef12013c0772e40d78be0f21577a495d8d99ec5fcb18c332d393d", size = 1759259 }, + { url = "https://files.pythonhosted.org/packages/4f/4b/c9f0bdda410912a6df79a789cb118fa29acae02a397794ead3c84adcda5c/deptry-0.25.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58d39279828dbf4efc1abb40bf50a71b21499c36759bed5a8d8a3c0e3149b091", size = 1872012 }, + { url = "https://files.pythonhosted.org/packages/72/9c/6f6f9125bac74b5d5d2af89536cbdb3fa159b6466aa097b74e7e85e8e030/deptry-0.25.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14bfcc28b4326ed8c6abb30691b19077d4ef8613cfba6c37ef5b1f471775bf6f", size = 1926575 }, + { url = "https://files.pythonhosted.org/packages/52/48/2a5e705a7f898295966ade67bd1223e2af96da433e25b39f6b9483ba2c7b/deptry-0.25.1-cp310-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:555f5f9a487899ec9bf301eecba1745e14d212c4b354f4d3a5fd691e907366d3", size = 2050816 }, + { url = "https://files.pythonhosted.org/packages/5f/c6/50f189a894e1f3bf21266299112c8a06cb731838976e1b9a9cadd0b4a86e/deptry-0.25.1-cp310-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:18d21b3545ab2bfec53f3f45c6f5f201d55f713323327f8d12674505469ae6b7", size = 2145416 }, + { url = "https://files.pythonhosted.org/packages/7a/6a/3f82f7a06217778282bc4456af1b4ffb3bc4b2c8e7891d00e8323f9ad0b8/deptry-0.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:b59a560cb7dffb21832a98bb80d33d614cfb5630ea36ce21833eabf4eae3df99", size = 1718489 }, + { url = "https://files.pythonhosted.org/packages/c7/7f/cd6b3ac8cf95f2f1c5c7a74ff6452e9098af89a9b56607381f677880641e/deptry-0.25.1-cp310-abi3-win_arm64.whl", hash = "sha256:6efffd8116fb9d2c45a251382ce4ce1c38dbb17179f581ec9231ed5390f7fc12", size = 1647020 }, +] + +[[package]] +name = "detect-secrets" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/67/382a863fff94eae5a0cf05542179169a1c49a4c8784a9480621e2066ca7d/detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a", size = 97351 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/5e/4f5fe4b89fde1dc3ed0eb51bd4ce4c0bca406246673d370ea2ad0c58d747/detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060", size = 120341 }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047 }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196 }, +] + +[[package]] +name = "dom-toml" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "domdf-python-tools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/91/cdad3f64c5bbe7650fc617f2f756b28827dd5f30b9f7b78597ce3e96fcd2/dom_toml-2.3.0.tar.gz", hash = "sha256:04d1138a7588119ec37ffe59e6474739a7ce7fcfcdf76555a064878ad82e3ae0", size = 13041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/cb/20465053f0f4854c261c038afce2703d71cc71d58035a53404128b1abfe7/dom_toml-2.3.0-py3-none-any.whl", hash = "sha256:bc2f985db6964de47b113783a6b18f1688693b2a47dec3c7451d3531ccab7029", size = 17505 }, +] + +[[package]] +name = "domdf-python-tools" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "natsort" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/8b/ab2d8a292bba8fe3135cacc8bfd3576710a14b8f2d0a8cde19130d5c9d21/domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298", size = 100458 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/11/208f72084084d3f6a2ed5ebfdfc846692c3f7ad6dce65e400194924f7eed/domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36", size = 126860 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024 }, +] + +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759 }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441 }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "license-expression" +version = "30.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boolean-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615 }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939 }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064 }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131 }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556 }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920 }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013 }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096 }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708 }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119 }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212 }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315 }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721 }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657 }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668 }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040 }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037 }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631 }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118 }, + { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127 }, + { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981 }, + { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885 }, + { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658 }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290 }, + { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234 }, + { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391 }, + { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787 }, + { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453 }, + { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264 }, + { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076 }, + { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242 }, + { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509 }, + { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957 }, + { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910 }, + { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197 }, + { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772 }, + { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868 }, +] + +[[package]] +name = "natsort" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 }, +] + +[[package]] +name = "packageurl-python" +version = "0.17.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/d6/3b5a4e3cfaef7a53869a26ceb034d1ff5e5c27c814ce77260a96d50ab7bb/packageurl_python-0.17.6.tar.gz", hash = "sha256:1252ce3a102372ca6f86eb968e16f9014c4ba511c5c37d95a7f023e2ca6e5c25", size = 50618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/2f/c7277b7615a93f51b5fbc1eacfc1b75e8103370e786fd8ce2abf6e5c04ab/packageurl_python-0.17.6-py3-none-any.whl", hash = "sha256:31a85c2717bc41dd818f3c62908685ff9eebcb68588213745b14a6ee9e7df7c9", size = 36776 }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 }, +] + +[[package]] +name = "pip" +version = "26.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/83/0d7d4e9efe3344b8e2fe25d93be44f64b65364d3c8d7bc6dc90198d5422e/pip-26.0.1.tar.gz", hash = "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8", size = 1812747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723 }, +] + +[[package]] +name = "pip-api" +version = "0.0.34" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pip" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/f1/ee85f8c7e82bccf90a3c7aad22863cc6e20057860a1361083cd2adacb92e/pip_api-0.0.34.tar.gz", hash = "sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625", size = 123017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/f7/ebf5003e1065fd00b4cbef53bf0a65c3d3e1b599b676d5383ccb7a8b88ba/pip_api-0.0.34-py3-none-any.whl", hash = "sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb", size = 120369 }, +] + +[[package]] +name = "pip-audit" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachecontrol", extra = ["filecache"] }, + { name = "cyclonedx-python-lib" }, + { name = "packaging" }, + { name = "pip-api" }, + { name = "pip-requirements-parser" }, + { name = "platformdirs" }, + { name = "requests" }, + { name = "rich" }, + { name = "tomli" }, + { name = "tomli-w" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/89/0e999b413facab81c33d118f3ac3739fd02c0622ccf7c4e82e37cebd8447/pip_audit-2.10.0.tar.gz", hash = "sha256:427ea5bf61d1d06b98b1ae29b7feacc00288a2eced52c9c58ceed5253ef6c2a4", size = 53776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/f3/4888f895c02afa085630a3a3329d1b18b998874642ad4c530e9a4d7851fe/pip_audit-2.10.0-py3-none-any.whl", hash = "sha256:16e02093872fac97580303f0848fa3ad64f7ecf600736ea7835a2b24de49613f", size = 61518 }, +] + +[[package]] +name = "pip-requirements-parser" +version = "32.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/2a/63b574101850e7f7b306ddbdb02cb294380d37948140eecd468fae392b54/pip-requirements-parser-32.0.1.tar.gz", hash = "sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3", size = 209359 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/d0/d04f1d1e064ac901439699ee097f58688caadea42498ec9c4b4ad2ef84ab/pip_requirements_parser-32.0.1-py3-none-any.whl", hash = "sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526", size = 35648 }, +] + +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "prek" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/ee/03e8180e3fda9de25b6480bd15cc2bde40d573868d50648b0e527b35562f/prek-0.3.8.tar.gz", hash = "sha256:434a214256516f187a3ab15f869d950243be66b94ad47987ee4281b69643a2d9", size = 400224 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/84/40d2ddf362d12c4cd4a25a8c89a862edf87cdfbf1422aa41aac8e315d409/prek-0.3.8-py3-none-linux_armv6l.whl", hash = "sha256:6fb646ada60658fa6dd7771b2e0fb097f005151be222f869dada3eb26d79ed33", size = 5226646 }, + { url = "https://files.pythonhosted.org/packages/e1/52/7308a033fa43b7e8e188797bd2b3b017c0f0adda70fa7af575b1f43ea888/prek-0.3.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3d7fdadb15efc19c09953c7a33cf2061a70f367d1e1957358d3ad5cc49d0616", size = 5620104 }, + { url = "https://files.pythonhosted.org/packages/ff/b1/f106ac000a91511a9cd80169868daf2f5b693480ef5232cec5517a38a512/prek-0.3.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:72728c3295e79ca443f8c1ec037d2a5b914ec73a358f69cf1bc1964511876bf8", size = 5199867 }, + { url = "https://files.pythonhosted.org/packages/b3/e9/970713f4b019f69de9844e1bab37b8ddb67558e410916f4eb5869a696165/prek-0.3.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:48efc28f2f53b5b8087efca9daaed91572d62df97d5f24a1c7a087fecb5017de", size = 5441801 }, + { url = "https://files.pythonhosted.org/packages/12/a4/7ef44032b181753e19452ec3b09abb3a32607cf6b0a0508f0604becaaf2b/prek-0.3.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6ca9d63bacbc448a5c18e955c78d3ac5176c3a17c3baacdd949b1a623e08a36", size = 5155107 }, + { url = "https://files.pythonhosted.org/packages/bd/77/4d9c8985dbba84149760785dfe07093ea1e29d710257dfb7c89615e2234c/prek-0.3.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1000f7029696b4fe712fb1fefd4c55b9c4de72b65509c8e50296370a06f9dc3f", size = 5566541 }, + { url = "https://files.pythonhosted.org/packages/1a/1a/81e6769ac1f7f8346d09ce2ab0b47cf06466acd9ff72e87e5d1f0d98cd32/prek-0.3.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ff0bed0e2c1286522987d982168a86cbbd0d069d840506a46c9fda983515517", size = 6552991 }, + { url = "https://files.pythonhosted.org/packages/6f/fa/ce2df0dd2dc75a9437a52463239d0782998943d7b04e191fb89b83016c34/prek-0.3.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fb087ac0ffda3ac65bbbae9a38326a7fd27ee007bb4a94323ce1eb539d8bbec", size = 5832972 }, + { url = "https://files.pythonhosted.org/packages/18/6b/9d4269df9073216d296244595a21c253b6475dfc9076c0bd2906be7a436c/prek-0.3.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:2e1e5e206ff7b31bd079cce525daddc96cd6bc544d20dc128921ad92f7a4c85d", size = 5448371 }, + { url = "https://files.pythonhosted.org/packages/60/1d/1e4d8a78abefa5b9d086e5a9f1638a74b5e540eec8a648d9946707701f29/prek-0.3.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:dcea3fe23832a4481bccb7c45f55650cb233be7c805602e788bb7dba60f2d861", size = 5270546 }, + { url = "https://files.pythonhosted.org/packages/77/07/34f36551a6319ae36e272bea63a42f59d41d2d47ab0d5fb00eb7b4e88e87/prek-0.3.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:4d25e647e9682f6818ab5c31e7a4b842993c14782a6ffcd128d22b784e0d677f", size = 5124032 }, + { url = "https://files.pythonhosted.org/packages/e3/01/6d544009bb655e709993411796af77339f439526db4f3b3509c583ad8eb9/prek-0.3.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:de528b82935e33074815acff3c7c86026754d1212136295bc88fe9c43b4231d5", size = 5432245 }, + { url = "https://files.pythonhosted.org/packages/54/96/1237ee269e9bfa283ffadbcba1f401f48a47aed2b2563eb1002740d6079d/prek-0.3.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:6d660f1c25a126e6d9f682fe61449441226514f412a4469f5d71f8f8cad56db2", size = 5950550 }, + { url = "https://files.pythonhosted.org/packages/ca/6b/a574411459049bc691047c9912f375deda10c44a707b6ce98df2b658f0b3/prek-0.3.8-py3-none-win32.whl", hash = "sha256:b0c291c577615d9f8450421dff0b32bfd77a6b0d223ee4115a1f820cb636fdf1", size = 4949501 }, + { url = "https://files.pythonhosted.org/packages/0c/b4/46b59fe49f635acd9f6530778ce577f9d8b49452835726a5311ffc902c67/prek-0.3.8-py3-none-win_amd64.whl", hash = "sha256:bc147fdbdd4ec33fc7a987b893ecb69b1413ac100d95c9889a70f3fd58c73d06", size = 5346551 }, + { url = "https://files.pythonhosted.org/packages/53/05/9cca1708bb8c65264124eb4b04251e0f65ce5bfc707080bb6b492d5a0df7/prek-0.3.8-py3-none-win_arm64.whl", hash = "sha256:a2614647aeafa817a5802ccb9561e92eedc20dcf840639a1b00826e2c2442515", size = 5190872 }, +] + +[[package]] +name = "py-serializable" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045 }, +] + +[[package]] +name = "pyenv-inspect" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/86/9b1c633bb6bbce17177ea368b8a2bfc2fef0329fe61fb582068e35fa0dcd/pyenv_inspect-0.5.0.tar.gz", hash = "sha256:acaec63227577ac6edd3e775036d7bb0077d8ed2ebce5021e176a732e20c84d3", size = 9358 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/3a/083f8de18901e9bd255068612899e7d71de7de451cd207f4d2c1530b8336/pyenv_inspect-0.5.0-py3-none-any.whl", hash = "sha256:ada3d3ea49b8604ff330d06a39ffe46df120d31c0bc9796392a41f2c3b34b08e", size = 7099 }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/7b/c0e1333b61d41c69e59e5366e727b18c4992688caf0de1be10b3e5265f6b/pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", size = 22785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218 }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876 }, +] + +[[package]] +name = "pytest-pretty" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/d7/c699e0be5401fe9ccad484562f0af9350b4e48c05acf39fb3dab1932128f/pytest_pretty-1.3.0.tar.gz", hash = "sha256:97e9921be40f003e40ae78db078d4a0c1ea42bf73418097b5077970c2cc43bf3", size = 219297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/85/2f97a1b65178b0f11c9c77c35417a4cc5b99a80db90dad4734a129844ea5/pytest_pretty-1.3.0-py3-none-any.whl", hash = "sha256:074b9d5783cef9571494543de07e768a4dda92a3e85118d6c7458c67297159b7", size = 5620 }, +] + +[[package]] +name = "pytest-sugar" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/4e/60fed105549297ba1a700e1ea7b828044842ea27d72c898990510b79b0e2/pytest-sugar-1.1.1.tar.gz", hash = "sha256:73b8b65163ebf10f9f671efab9eed3d56f20d2ca68bda83fa64740a92c08f65d", size = 16533 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/d5/81d38a91c1fdafb6711f053f5a9b92ff788013b19821257c2c38c1e132df/pytest_sugar-1.1.1-py3-none-any.whl", hash = "sha256:2f8319b907548d5b9d03a171515c1d43d2e38e32bd8182a1781eb20b43344cc8", size = 11440 }, +] + +[[package]] +name = "python-discovery" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +] + +[[package]] +name = "regex" +version = "2026.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/28/b972a4d3df61e1d7bcf1b59fdb3cddef22f88b6be43f161bb41ebc0e4081/regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", size = 490434 }, + { url = "https://files.pythonhosted.org/packages/84/20/30041446cf6dc3e0eab344fc62770e84c23b6b68a3b657821f9f80cb69b4/regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", size = 292061 }, + { url = "https://files.pythonhosted.org/packages/62/c8/3baa06d75c98c46d4cc4262b71fd2edb9062b5665e868bca57859dadf93a/regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", size = 289628 }, + { url = "https://files.pythonhosted.org/packages/31/87/3accf55634caad8c0acab23f5135ef7d4a21c39f28c55c816ae012931408/regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", size = 796651 }, + { url = "https://files.pythonhosted.org/packages/f6/0c/aaa2c83f34efedbf06f61cb1942c25f6cf1ee3b200f832c4d05f28306c2e/regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", size = 865916 }, + { url = "https://files.pythonhosted.org/packages/d9/f6/8c6924c865124643e8f37823eca845dc27ac509b2ee58123685e71cd0279/regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", size = 912287 }, + { url = "https://files.pythonhosted.org/packages/11/0e/a9f6f81013e0deaf559b25711623864970fe6a098314e374ccb1540a4152/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", size = 801126 }, + { url = "https://files.pythonhosted.org/packages/71/61/3a0cc8af2dc0c8deb48e644dd2521f173f7e6513c6e195aad9aa8dd77ac5/regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", size = 776788 }, + { url = "https://files.pythonhosted.org/packages/64/0b/8bb9cbf21ef7dee58e49b0fdb066a7aded146c823202e16494a36777594f/regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", size = 785184 }, + { url = "https://files.pythonhosted.org/packages/99/c2/d3e80e8137b25ee06c92627de4e4d98b94830e02b3e6f81f3d2e3f504cf5/regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", size = 859913 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/9d5d876157d969c804622456ef250017ac7a8f83e0e14f903b9e6df5ce95/regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", size = 765732 }, + { url = "https://files.pythonhosted.org/packages/82/80/b568935b4421388561c8ed42aff77247285d3ae3bb2a6ca22af63bae805e/regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", size = 852152 }, + { url = "https://files.pythonhosted.org/packages/39/29/f0f81217e21cd998245da047405366385d5c6072048038a3d33b37a79dc0/regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", size = 789076 }, + { url = "https://files.pythonhosted.org/packages/49/1d/1d957a61976ab9d4e767dd4f9d04b66cc0c41c5e36cf40e2d43688b5ae6f/regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", size = 266700 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/bf575d396aeb58ea13b06ef2adf624f65b70fafef6950a80fc3da9cae3bc/regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", size = 277768 }, + { url = "https://files.pythonhosted.org/packages/c9/27/049df16ec6a6828ccd72add3c7f54b4df029669bea8e9817df6fff58be90/regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", size = 270568 }, + { url = "https://files.pythonhosted.org/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273 }, + { url = "https://files.pythonhosted.org/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954 }, + { url = "https://files.pythonhosted.org/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487 }, + { url = "https://files.pythonhosted.org/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646 }, + { url = "https://files.pythonhosted.org/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904 }, + { url = "https://files.pythonhosted.org/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304 }, + { url = "https://files.pythonhosted.org/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126 }, + { url = "https://files.pythonhosted.org/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772 }, + { url = "https://files.pythonhosted.org/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228 }, + { url = "https://files.pythonhosted.org/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032 }, + { url = "https://files.pythonhosted.org/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714 }, + { url = "https://files.pythonhosted.org/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078 }, + { url = "https://files.pythonhosted.org/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181 }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690 }, + { url = "https://files.pythonhosted.org/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733 }, + { url = "https://files.pythonhosted.org/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565 }, + { url = "https://files.pythonhosted.org/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126 }, + { url = "https://files.pythonhosted.org/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334 }, + { url = "https://files.pythonhosted.org/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691 }, + { url = "https://files.pythonhosted.org/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227 }, + { url = "https://files.pythonhosted.org/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435 }, + { url = "https://files.pythonhosted.org/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358 }, + { url = "https://files.pythonhosted.org/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549 }, + { url = "https://files.pythonhosted.org/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364 }, + { url = "https://files.pythonhosted.org/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221 }, + { url = "https://files.pythonhosted.org/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530 }, + { url = "https://files.pythonhosted.org/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989 }, + { url = "https://files.pythonhosted.org/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241 }, + { url = "https://files.pythonhosted.org/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921 }, + { url = "https://files.pythonhosted.org/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240 }, + { url = "https://files.pythonhosted.org/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440 }, + { url = "https://files.pythonhosted.org/packages/f0/f5/ed97c2dc47b5fbd4b73c0d7d75f9ebc8eca139f2bbef476bba35f28c0a77/regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", size = 490343 }, + { url = "https://files.pythonhosted.org/packages/80/e9/de4828a7385ec166d673a5790ad06ac48cdaa98bc0960108dd4b9cc1aef7/regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", size = 291909 }, + { url = "https://files.pythonhosted.org/packages/b4/d6/5cfbfc97f3201a4d24b596a77957e092030dcc4205894bc035cedcfce62f/regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", size = 289692 }, + { url = "https://files.pythonhosted.org/packages/8e/ac/f2212d9fd56fe897e36d0110ba30ba2d247bd6410c5bd98499c7e5a1e1f2/regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", size = 796979 }, + { url = "https://files.pythonhosted.org/packages/c9/e3/a016c12675fbac988a60c7e1c16e67823ff0bc016beb27bd7a001dbdabc6/regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", size = 866744 }, + { url = "https://files.pythonhosted.org/packages/af/a4/0b90ca4cf17adc3cb43de80ec71018c37c88ad64987e8d0d481a95ca60b5/regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", size = 911613 }, + { url = "https://files.pythonhosted.org/packages/8e/3b/2b3dac0b82d41ab43aa87c6ecde63d71189d03fe8854b8ca455a315edac3/regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", size = 800551 }, + { url = "https://files.pythonhosted.org/packages/25/fe/5365eb7aa0e753c4b5957815c321519ecab033c279c60e1b1ae2367fa810/regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", size = 776911 }, + { url = "https://files.pythonhosted.org/packages/aa/b3/7fb0072156bba065e3b778a7bc7b0a6328212be5dd6a86fd207e0c4f2dab/regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", size = 785751 }, + { url = "https://files.pythonhosted.org/packages/02/1a/9f83677eb699273e56e858f7bd95acdbee376d42f59e8bfca2fd80d79df3/regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", size = 860484 }, + { url = "https://files.pythonhosted.org/packages/3b/7a/93937507b61cfcff8b4c5857f1b452852b09f741daa9acae15c971d8554e/regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", size = 765939 }, + { url = "https://files.pythonhosted.org/packages/86/ea/81a7f968a351c6552b1670ead861e2a385be730ee28402233020c67f9e0f/regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", size = 851417 }, + { url = "https://files.pythonhosted.org/packages/4c/7e/323c18ce4b5b8f44517a36342961a0306e931e499febbd876bb149d900f0/regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", size = 789056 }, + { url = "https://files.pythonhosted.org/packages/c0/af/e7510f9b11b1913b0cd44eddb784b2d650b2af6515bfce4cffcc5bfd1d38/regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", size = 272130 }, + { url = "https://files.pythonhosted.org/packages/9a/51/57dae534c915e2d3a21490e88836fa2ae79dde3b66255ecc0c0a155d2c10/regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", size = 280992 }, + { url = "https://files.pythonhosted.org/packages/0a/5e/abaf9f4c3792e34edb1434f06717fae2b07888d85cb5cec29f9204931bf8/regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", size = 273563 }, + { url = "https://files.pythonhosted.org/packages/ff/06/35da85f9f217b9538b99cbb170738993bcc3b23784322decb77619f11502/regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", size = 494191 }, + { url = "https://files.pythonhosted.org/packages/54/5b/1bc35f479eef8285c4baf88d8c002023efdeebb7b44a8735b36195486ae7/regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", size = 293877 }, + { url = "https://files.pythonhosted.org/packages/39/5b/f53b9ad17480b3ddd14c90da04bfb55ac6894b129e5dea87bcaf7d00e336/regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", size = 292410 }, + { url = "https://files.pythonhosted.org/packages/bb/56/52377f59f60a7c51aa4161eecf0b6032c20b461805aca051250da435ffc9/regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", size = 811831 }, + { url = "https://files.pythonhosted.org/packages/dd/63/8026310bf066f702a9c361f83a8c9658f3fe4edb349f9c1e5d5273b7c40c/regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", size = 871199 }, + { url = "https://files.pythonhosted.org/packages/20/9f/a514bbb00a466dbb506d43f187a04047f7be1505f10a9a15615ead5080ee/regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", size = 917649 }, + { url = "https://files.pythonhosted.org/packages/cb/6b/8399f68dd41a2030218839b9b18360d79b86d22b9fab5ef477c7f23ca67c/regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", size = 816388 }, + { url = "https://files.pythonhosted.org/packages/1e/9c/103963f47c24339a483b05edd568594c2be486188f688c0170fd504b2948/regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", size = 785746 }, + { url = "https://files.pythonhosted.org/packages/fa/ee/7f6054c0dec0cee3463c304405e4ff42e27cff05bf36fcb34be549ab17bd/regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", size = 801483 }, + { url = "https://files.pythonhosted.org/packages/30/c2/51d3d941cf6070dc00c3338ecf138615fc3cce0421c3df6abe97a08af61a/regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", size = 866331 }, + { url = "https://files.pythonhosted.org/packages/16/e8/76d50dcc122ac33927d939f350eebcfe3dbcbda96913e03433fc36de5e63/regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", size = 772673 }, + { url = "https://files.pythonhosted.org/packages/a5/6e/5f6bf75e20ea6873d05ba4ec78378c375cbe08cdec571c83fbb01606e563/regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", size = 857146 }, + { url = "https://files.pythonhosted.org/packages/0b/33/3c76d9962949e487ebba353a18e89399f292287204ac8f2f4cfc3a51c233/regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", size = 803463 }, + { url = "https://files.pythonhosted.org/packages/19/eb/ef32dcd2cb69b69bc0c3e55205bce94a7def48d495358946bc42186dcccc/regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", size = 275709 }, + { url = "https://files.pythonhosted.org/packages/a0/86/c291bf740945acbf35ed7dbebf8e2eea2f3f78041f6bd7cdab80cb274dc0/regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", size = 285622 }, + { url = "https://files.pythonhosted.org/packages/d5/e7/ec846d560ae6a597115153c02ca6138a7877a1748b2072d9521c10a93e58/regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", size = 275773 }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947 }, +] + +[[package]] +name = "requirements-parser" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/96/fb6dbfebb524d5601d359a47c78fe7ba1eef90fc4096404aa60c9a906fbb/requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418", size = 22630 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/60/50fbb6ffb35f733654466f1a90d162bcbea358adc3b0871339254fbc37b2/requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14", size = 14782 }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458 }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676 }, +] + +[[package]] +name = "ruff" +version = "0.15.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362 }, + { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122 }, + { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005 }, + { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450 }, + { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597 }, + { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645 }, + { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289 }, + { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266 }, + { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416 }, + { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053 }, + { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302 }, + { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074 }, + { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051 }, + { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964 }, + { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044 }, + { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607 }, +] + +[[package]] +name = "rust-just" +version = "1.49.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/f2/c1612952bb4de508e89a1a10ff8bc7fbf18160bf75bcf502264e04e0c3ba/rust_just-1.49.0.tar.gz", hash = "sha256:a3b5f16f5e131b7ea86720510798688c146ae4859a92410e1ed5d13e79ddcd0f", size = 1912238 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/d9/5f7fa48d2903345a885a4d93a5c3a75d14acad8211a6e41bc24c6221f479/rust_just-1.49.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c91cad7bbb9280b74f0f6761b4b9f91ab8b30e6fd29885d463fcee011c1c28a8", size = 1976290 }, + { url = "https://files.pythonhosted.org/packages/2e/2a/bcac6a5091dbbdac5536a28f5c5747a7ce4d251e2273a2586a03b6e15fd1/rust_just-1.49.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:655b50c40a6a43464a3b217874d81623143c95fecfcaca476d5552d3cdb035b3", size = 1839665 }, + { url = "https://files.pythonhosted.org/packages/e3/ec/8dff9bcc9327cea9990e0c88932e554131afcf4b593e5163de1c994bd325/rust_just-1.49.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cd8b7a1d7a84f95ebba73a2038eed1d89e2ae397f80e89c99a25e34808206f8", size = 1927171 }, + { url = "https://files.pythonhosted.org/packages/93/a1/d7cc40ec13a702f1fecc4f14d5d6e34f31e557a8cf4e14fd4de0e364d9eb/rust_just-1.49.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:505f7d9c132f8ba747b35c7ba92c5bca13950875b4040a4c0c1a5aa1b94bc3f6", size = 1904953 }, + { url = "https://files.pythonhosted.org/packages/fb/0f/19be2e9f6682ae4941b4ea98cefa9c6f3f41ca32f134407651fdc52c38c9/rust_just-1.49.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e82381559658f83c5888a36f420b4bec6e4abdecef7ad40d4c74a90d80551e96", size = 2108508 }, + { url = "https://files.pythonhosted.org/packages/58/7f/d073eaf6729e4c239c9935bd1a94ea19e9dd9504242e077d651e41fdb386/rust_just-1.49.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3dd978725e4ed8d34d0518acf34e6cd9e215bb24737b62eed87fec3d4747507", size = 2177355 }, + { url = "https://files.pythonhosted.org/packages/5e/69/d0331a382c8c6a5390164c591dba4ca74e31c903389fc92dea895654ee67/rust_just-1.49.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad50c7d05fedc4d87abd6cc44127e1ddc246be56f5f11fad05ac00e0b66c33e4", size = 2120914 }, + { url = "https://files.pythonhosted.org/packages/25/f4/997605402b8502f7637875c1159a6dd9c0dd74d0493d49cf858ea5463b6a/rust_just-1.49.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2e0f03694f030f3f39f7bba53545d6038eddb87fa0a3383cd9f3a55dcf4ed26", size = 2090756 }, + { url = "https://files.pythonhosted.org/packages/c0/bc/bd90073c7f2725eeef93a9816c8c2d49410339d1863d385071b7960677e5/rust_just-1.49.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d105edbcc02e278fa3eb6730a8be2c4576e5ce4f52f43706ae5e7a07af112278", size = 1949099 }, + { url = "https://files.pythonhosted.org/packages/a8/54/cf57ec444c1fa851525d651f67f7eb4c1b52e701568638d2784fe690fd62/rust_just-1.49.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6bc0c84f1c1bc5c13b66f6947e21e7828f0df0048cff40565ed7b574e04cfd16", size = 1931798 }, + { url = "https://files.pythonhosted.org/packages/ef/a3/7a5ec8e401f4b069d6577c8cc72592264ec220eabd0e767db7aad22a6586/rust_just-1.49.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:abe84305fc4591c95f4dc0a02f61228a4bf64a4a5d5558cbf4777401147f9681", size = 2080900 }, + { url = "https://files.pythonhosted.org/packages/98/65/817e047a93e0d9c4faa5eb9de05db4f1631f419f5dccc94a4e16dda79309/rust_just-1.49.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a814fccd210727d587ba058efb2c54e772a102a73bc729759cf96667d1d4f599", size = 2155307 }, + { url = "https://files.pythonhosted.org/packages/ef/e0/e7678714597383cf9ccd76ed7819b1cf6e3d079c80bdf476496634be36de/rust_just-1.49.0-py3-none-win32.whl", hash = "sha256:9ace425355867b3249b9c61ed7c23a2f482259345de3e649c262deb1263c321d", size = 1855815 }, + { url = "https://files.pythonhosted.org/packages/08/85/ce1299f161ea7094c175953d1cb17aaf19900e9ec23edfbe728b27cd2cbd/rust_just-1.49.0-py3-none-win_amd64.whl", hash = "sha256:e34a1845394d947f2f7e47ba8c9fe9d323bb6c21af4aa5dca5168507ecdb7eba", size = 2064187 }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274 }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742 }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/2f/6152d2e409ffaab18b397ac1cde0920dc071f9afb76125a2d496c80b9976/sphinx_autodoc_typehints-3.10.0.tar.gz", hash = "sha256:7b821a123852176b2ed4f2cb9da8db06531a15b8098a4c7350c68febb7669bd0", size = 72801 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/e9/b75897138c5611213472b5726dbb6106ac6192dd31f219cd154bacefa498/sphinx_autodoc_typehints-3.10.0-py3-none-any.whl", hash = "sha256:2176424f9e1ce3054d9016ac16b51d4b9febffd8cad8ece3b7912b2c4646759f", size = 38703 }, +] + +[[package]] +name = "sphinx-pyproject" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dom-toml" }, + { name = "domdf-python-tools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076 }, +] + +[[package]] +name = "sphinx-rtd-size" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/fa/6498e96b0e9c99010eb952ef2878c55e0b453cf598ca12719d929d13a484/sphinx-rtd-size-0.2.0.tar.gz", hash = "sha256:00168969c870923dca449c3264741a0b77607e3419b9af1faafcf008dab9a730", size = 3063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/28/ec767ea91968c9337a81c4e0700a103c5794d692c1f3889699b0873f5099/sphinx_rtd_size-0.2.0-py3-none-any.whl", hash = "sha256:fb1c78fbab1880ae6c2092359aa7401b61716dcb97c8694b7b990f69a98dac62", size = 4445 }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "termcolor" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734 }, +] + +[[package]] +name = "toml-sort" +version = "0.24.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/c5/d6f650fdcf8e1f83096815fa67fb13a9a345b99da6015c60c4b7e4a8ea2b/toml_sort-0.24.4.tar.gz", hash = "sha256:429b69f5b98b7047a11380c80ecf0838556bdea1a8902d0be564961c48841423", size = 17793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/5a/1f0e54df4eacf0f4d8f94ba50cf72be33d2a3f04babdfb1931bead48a0ab/toml_sort-0.24.4-py3-none-any.whl", hash = "sha256:125aa5fb94f33c542c6901040456145dd38f79bbb310b56b436a93057d30a739", size = 16577 }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924 }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018 }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948 }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341 }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159 }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290 }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141 }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847 }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088 }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866 }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887 }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704 }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628 }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180 }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674 }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976 }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755 }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265 }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726 }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859 }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713 }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084 }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973 }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223 }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973 }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082 }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490 }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263 }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736 }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717 }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461 }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855 }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144 }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683 }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196 }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393 }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583 }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675 }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310 }, +] + +[[package]] +name = "tox" +version = "4.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "python-discovery" }, + { name = "tomli-w" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/fb/d7d634eb513f741ffd40f4c262b7feea19d5c616882eb554045c620670a6/tox-4.52.1.tar.gz", hash = "sha256:297e71ea0ae4ef3acc45cb5fdf080b74537e6ecb5eea7d4646fa7322ca10473e", size = 273730 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/70/0d4fb1eefa05a24ca2f58272b4c4718090dd5ed7e38b54b9a7e757bfafc8/tox-4.52.1-py3-none-any.whl", hash = "sha256:3c4eef0a64f319df0b67dacdb7edcfeda87c8cc722581af5d98dd54f3ffdd8ef", size = 212179 }, +] + +[[package]] +name = "tox-pyenv-redux" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tox" }, + { name = "virtualenv-pyenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/d0/aa412f0e6eb8437941e078eb3523f01600626b3dea595117f786445ddee6/tox_pyenv_redux-1.2.0.tar.gz", hash = "sha256:fdabac723a99710f936031fe5e7a0125205441536190512cbd59d6e0a9e57440", size = 5274 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4f/2c0056a4acac76d3b0be6ad0382849df6b150610861d5b09e2ca8b06526e/tox_pyenv_redux-1.2.0-py3-none-any.whl", hash = "sha256:a583e430a01f576b34c3ed3c428981f73b6e30cd45c114c42ea0e283dc90534a", size = 4545 }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, +] + +[[package]] +name = "uv" +version = "0.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/f3/8aceeab67ea69805293ab290e7ca8cc1b61a064d28b8a35c76d8eba063dd/uv-0.11.6.tar.gz", hash = "sha256:e3b21b7e80024c95ff339fcd147ac6fc3dd98d3613c9d45d3a1f4fd1057f127b", size = 4073298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/fe/4b61a3d5ad9d02e8a4405026ccd43593d7044598e0fa47d892d4dafe44c9/uv-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:ada04dcf89ddea5b69d27ac9cdc5ef575a82f90a209a1392e930de504b2321d6", size = 23780079 }, + { url = "https://files.pythonhosted.org/packages/52/db/d27519a9e1a5ffee9d71af1a811ad0e19ce7ab9ae815453bef39dd479389/uv-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5be013888420f96879c6e0d3081e7bcf51b539b034a01777041934457dfbedf3", size = 23214721 }, + { url = "https://files.pythonhosted.org/packages/a6/8f/4399fa8b882bd7e0efffc829f73ab24d117d490a93e6bc7104a50282b854/uv-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ffa5dc1cbb52bdce3b8447e83d1601a57ad4da6b523d77d4b47366db8b1ceb18", size = 21750109 }, + { url = "https://files.pythonhosted.org/packages/32/07/5a12944c31c3dda253632da7a363edddb869ed47839d4d92a2dc5f546c93/uv-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:bfb107b4dade1d2c9e572992b06992d51dd5f2136eb8ceee9e62dd124289e825", size = 23551146 }, + { url = "https://files.pythonhosted.org/packages/79/5b/2ec8b0af80acd1016ed596baf205ddc77b19ece288473b01926c4a9cf6db/uv-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:9e2fe7ce12161d8016b7deb1eaad7905a76ff7afec13383333ca75e0c4b5425d", size = 23331192 }, + { url = "https://files.pythonhosted.org/packages/62/7d/eea35935f2112b21c296a3e42645f3e4b1aa8bcd34dcf13345fbd55134b7/uv-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ed9c6f70c25e8dfeedddf4eddaf14d353f5e6b0eb43da9a14d3a1033d51d915", size = 23337686 }, + { url = "https://files.pythonhosted.org/packages/21/47/2584f5ab618f6ebe9bdefb2f765f2ca8540e9d739667606a916b35449eec/uv-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d68a013e609cebf82077cbeeb0809ed5e205257814273bfd31e02fc0353bbfc2", size = 25008139 }, + { url = "https://files.pythonhosted.org/packages/95/81/497ae5c1d36355b56b97dc59f550c7e89d0291c163a3f203c6f341dff195/uv-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93f736dddca03dae732c6fdea177328d3bc4bf137c75248f3d433c57416a4311", size = 25712458 }, + { url = "https://files.pythonhosted.org/packages/3c/1c/74083238e4fab2672b63575b9008f1ea418b02a714bcfcf017f4f6a309b6/uv-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e96a66abe53fced0e3389008b8d2eff8278cfa8bb545d75631ae8ceb9c929aba", size = 24915507 }, + { url = "https://files.pythonhosted.org/packages/5a/ee/e14fe10ba455a823ed18233f12de6699a601890905420b5c504abf115116/uv-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b096311b2743b228df911a19532b3f18fa420bf9530547aecd6a8e04bbfaccd", size = 24971011 }, + { url = "https://files.pythonhosted.org/packages/3c/a1/7b9c83eaadf98e343317ff6384a7227a4855afd02cdaf9696bcc71ee6155/uv-0.11.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:904d537b4a6e798015b4a64ff5622023bd4601b43b6cd1e5f423d63471f5e948", size = 23640234 }, + { url = "https://files.pythonhosted.org/packages/d6/51/75ccdd23e76ff1703b70eb82881cd5b4d2a954c9679f8ef7e0136ef2cfab/uv-0.11.6-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:4ed8150c26b5e319381d75ae2ce6aba1e9c65888f4850f4e3b3fa839953c90a5", size = 24452664 }, + { url = "https://files.pythonhosted.org/packages/4d/86/ace80fe47d8d48b5e3b5aee0b6eb1a49deaacc2313782870250b3faa36f5/uv-0.11.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1c9218c8d4ac35ca6e617fb0951cc0ab2d907c91a6aea2617de0a5494cf162c0", size = 24494599 }, + { url = "https://files.pythonhosted.org/packages/05/2d/4b642669b56648194f026de79bc992cbfc3ac2318b0a8d435f3c284934e8/uv-0.11.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:9e211c83cc890c569b86a4183fcf5f8b6f0c7adc33a839b699a98d30f1310d3a", size = 24159150 }, + { url = "https://files.pythonhosted.org/packages/ae/24/7eecd76fe983a74fed1fc700a14882e70c4e857f1d562a9f2303d4286c12/uv-0.11.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d2a1d2089afdf117ad19a4c1dd36b8189c00ae1ad4135d3bfbfced82342595cf", size = 25164324 }, + { url = "https://files.pythonhosted.org/packages/27/e0/bbd4ba7c2e5067bbba617d87d306ec146889edaeeaa2081d3e122178ca08/uv-0.11.6-py3-none-win32.whl", hash = "sha256:6e8344f38fa29f85dcfd3e62dc35a700d2448f8e90381077ef393438dcd5012e", size = 22865693 }, + { url = "https://files.pythonhosted.org/packages/a5/33/1983ce113c538a856f2d620d16e39691962ecceef091a84086c5785e32e5/uv-0.11.6-py3-none-win_amd64.whl", hash = "sha256:a28bea69c1186303d1200f155c7a28c449f8a4431e458fcf89360cc7ef546e40", size = 25371258 }, + { url = "https://files.pythonhosted.org/packages/35/01/be0873f44b9c9bc250fcbf263367fcfc1f59feab996355bcb6b52fff080d/uv-0.11.6-py3-none-win_arm64.whl", hash = "sha256:a78f6d64b9950e24061bc7ec7f15ff8089ad7f5a976e7b65fcadce58fe02f613", size = 23869585 }, +] + +[[package]] +name = "validate-pyproject" +version = "0.25" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/d8/45a82408619c306d0083afcf6142617201b2647a82e9a7d4c902e837274c/validate_pyproject-0.25.tar.gz", hash = "sha256:e68c12d1cb0d8ddc269ffc42875a81727ddb7865000aa6d2f77d833b55c53f0b", size = 118662 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ee/e9c95cda829131f71a8dff5ce0406059fd16e591c074414e31ada19ba7c3/validate_pyproject-0.25-py3-none-any.whl", hash = "sha256:f9d05e2686beff82f9ea954f582306b036ced3d3feb258c1110f2c2a495b1981", size = 54942 }, +] + +[[package]] +name = "verbex" +version = "3.0.0" +source = { editable = "." } +dependencies = [ + { name = "beartype" }, + { name = "regex" }, +] + +[package.optional-dependencies] +dev = [ + { name = "deptry" }, + { name = "detect-secrets" }, + { name = "pip-audit" }, + { name = "prek" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-pretty" }, + { name = "pytest-sugar" }, + { name = "ruff" }, + { name = "rust-just" }, + { name = "toml-sort" }, + { name = "tox" }, + { name = "tox-pyenv-redux" }, + { name = "uv" }, + { name = "validate-pyproject" }, +] +docs = [ + { name = "autodocsumm" }, + { name = "sphinx" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-pyproject" }, + { name = "sphinx-rtd-size" }, + { name = "sphinx-rtd-theme" }, +] + +[package.metadata] +requires-dist = [ + { name = "autodocsumm", marker = "extra == 'docs'" }, + { name = "beartype" }, + { name = "deptry", marker = "extra == 'dev'", specifier = ">=0.25.1" }, + { name = "detect-secrets", marker = "extra == 'dev'", specifier = ">=1.5.0" }, + { name = "pip-audit", marker = "extra == 'dev'" }, + { name = "prek", marker = "extra == 'dev'", specifier = ">=0.3.8" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, + { name = "pytest-pretty", marker = "extra == 'dev'" }, + { name = "pytest-sugar", marker = "extra == 'dev'", specifier = ">=1.1.1" }, + { name = "regex", specifier = ">=2026.4.4" }, + { name = "ruff", marker = "extra == 'dev'" }, + { name = "rust-just", marker = "extra == 'dev'", specifier = ">=1.49.0" }, + { name = "sphinx", marker = "extra == 'docs'" }, + { name = "sphinx-autodoc-typehints", marker = "extra == 'docs'" }, + { name = "sphinx-pyproject", marker = "extra == 'docs'" }, + { name = "sphinx-rtd-size", marker = "extra == 'docs'" }, + { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, + { name = "toml-sort", marker = "extra == 'dev'" }, + { name = "tox", marker = "extra == 'dev'" }, + { name = "tox-pyenv-redux", marker = "extra == 'dev'" }, + { name = "uv", marker = "extra == 'dev'" }, + { name = "validate-pyproject", marker = "extra == 'dev'" }, +] +provides-extras = ["dev", "optional", "docs"] + +[[package]] +name = "virtualenv" +version = "21.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/c5/aff062c66b42e2183201a7ace10c6b2e959a9a16525c8e8ca8e59410d27a/virtualenv-21.2.1.tar.gz", hash = "sha256:b66ffe81301766c0d5e2208fc3576652c59d44e7b731fc5f5ed701c9b537fa78", size = 5844770 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/0e/f083a76cb590e60dff3868779558eefefb8dfb7c9ed020babc7aa014ccbf/virtualenv-21.2.1-py3-none-any.whl", hash = "sha256:bd16b49c53562b28cf1a3ad2f36edb805ad71301dee70ddc449e5c88a9f919a2", size = 5828326 }, +] + +[[package]] +name = "virtualenv-pyenv" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyenv-inspect" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/77/29169ad22804ecb0c07b634bb0aa5476a52733f5b779018846fa22f6b4e6/virtualenv_pyenv-0.7.0.tar.gz", hash = "sha256:ead3d15379dee54aabaeaec25fab384583b033ec7a88f5e9ea4af7328be7f354", size = 9650 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/a2/fc8ed1a829cf4878338492c63f6a3a97b0bd2f56ea9dc4e6af7b8c4a187f/virtualenv_pyenv-0.7.0-py3-none-any.whl", hash = "sha256:3bc9982ce86dffa44548512aac4b07cf0524f176a380066c8894dadfb1eef44f", size = 7199 }, +] From cdae8a3eedd2f0fd14d05a5a80a377e97835e137 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Fri, 10 Apr 2026 20:48:36 -0400 Subject: [PATCH 87/90] Remove legacy dist zip artifacts --- dist/Verbex-1.0.2.win-amd64.zip | Bin 27017 -> 0 bytes dist/Verbex-1.0.3.win-amd64.zip | Bin 27010 -> 0 bytes dist/Verbex-1.1.0.win-amd64.zip | Bin 27913 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dist/Verbex-1.0.2.win-amd64.zip delete mode 100644 dist/Verbex-1.0.3.win-amd64.zip delete mode 100644 dist/Verbex-1.1.0.win-amd64.zip diff --git a/dist/Verbex-1.0.2.win-amd64.zip b/dist/Verbex-1.0.2.win-amd64.zip deleted file mode 100644 index f7a260975d7feda6972cb000aae92ba03f975d77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27017 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
76nan=rzuu;La;G1l>#UJM+P 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 KIdbD4|NaNF7TQ+; diff --git a/dist/Verbex-1.1.0.win-amd64.zip b/dist/Verbex-1.1.0.win-amd64.zip deleted file mode 100644 index 374059f0fac7792baa899dd44959497e6369f7bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27913 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 From 8fdd6cf5898ecf0d4441c5a844f1fd4b8c14300c Mon Sep 17 00:00:00 2001 From: rbroderi Date: Fri, 10 Apr 2026 20:48:59 -0400 Subject: [PATCH 88/90] Remove legacy dist zip artifacts --- .github/workflows/create-release-from-tag.yml | 23 +++++++++++++++++++ .github/workflows/publish-pypi.yml | 6 +++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/create-release-from-tag.yml diff --git a/.github/workflows/create-release-from-tag.yml b/.github/workflows/create-release-from-tag.yml new file mode 100644 index 0000000..73186e9 --- /dev/null +++ b/.github/workflows/create-release-from-tag.yml @@ -0,0 +1,23 @@ +name: Create Release From Tag +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (for example: v3.0.0)" + required: true + type: string +permissions: + contents: write +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.inputs.tag || github.ref_name }} + generate_release_notes: true + make_latest: true diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 9019305..271ecfc 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -22,10 +22,12 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install twine + - name: Clean dist directory + run: rm -rf dist - name: Build sdist and wheel run: uv build --no-sources - name: Check distributions - run: python -m twine check dist/* + run: python -m twine check dist/*.tar.gz dist/*.whl - name: Upload distributions artifact uses: actions/upload-artifact@v7 with: @@ -49,4 +51,4 @@ jobs: - name: Set up uv uses: astral-sh/setup-uv@v7 - name: Publish package to PyPI - run: uv publish --trusted-publishing always --check-url https://pypi.org/simple dist/* + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple dist/*.tar.gz dist/*.whl From 671b44137c2873b1b2804d827359fd9df4c82358 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Fri, 10 Apr 2026 20:54:57 -0400 Subject: [PATCH 89/90] fix github actions --- .github/workflows/create-release-from-tag.yml | 70 +++++++++++ .github/workflows/publish-pypi.yml | 54 --------- .github/workflows/pypi.yaml | 110 ++++++++++++++++++ 3 files changed, 180 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/publish-pypi.yml create mode 100644 .github/workflows/pypi.yaml diff --git a/.github/workflows/create-release-from-tag.yml b/.github/workflows/create-release-from-tag.yml index 73186e9..813fd43 100644 --- a/.github/workflows/create-release-from-tag.yml +++ b/.github/workflows/create-release-from-tag.yml @@ -12,12 +12,82 @@ on: permissions: contents: write jobs: + build-sdist: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Build sdist + run: uv build --no-sources --sdist + - name: Upload sdist artifact + uses: actions/upload-artifact@v7 + with: + name: release-sdist + path: dist/*.tar.gz + build-wheels: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Build wheel + run: uv build --no-sources --wheel + - name: Upload wheel artifact + uses: actions/upload-artifact@v7 + with: + name: release-wheel-${{ matrix.python-version }} + path: dist/*.whl create-release: + needs: [build-sdist, build-wheels] runs-on: ubuntu-latest steps: + - name: Download sdist artifact + uses: actions/download-artifact@v8 + with: + name: release-sdist + path: dist/ + - name: Download wheel artifacts + uses: actions/download-artifact@v8 + with: + pattern: release-wheel-* + path: dist/wheels + - name: Prepare release assets + run: | + mkdir -p dist/release + cp dist/*.tar.gz dist/release/ + python - <<'PY' + import hashlib + import shutil + from pathlib import Path + + seen = set() + out = Path("dist/release") + for wheel in sorted(Path("dist/wheels").rglob("*.whl")): + digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + if digest in seen: + continue + seen.add(digest) + shutil.copy2(wheel, out / wheel.name) + PY - name: Create GitHub release uses: softprops/action-gh-release@v2 with: tag_name: ${{ github.event.inputs.tag || github.ref_name }} generate_release_notes: true make_latest: true + files: dist/release/* diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml deleted file mode 100644 index 271ecfc..0000000 --- a/.github/workflows/publish-pypi.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Publish to PyPI -"on": - release: - types: [published] - workflow_dispatch: -permissions: - contents: read -jobs: - build: - name: Build distributions - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - name: Install check tooling - run: | - python -m pip install --upgrade pip - python -m pip install twine - - name: Clean dist directory - run: rm -rf dist - - name: Build sdist and wheel - run: uv build --no-sources - - name: Check distributions - run: python -m twine check dist/*.tar.gz dist/*.whl - - name: Upload distributions artifact - uses: actions/upload-artifact@v7 - with: - name: python-distributions - path: dist/ - publish: - name: Publish to PyPI - needs: build - runs-on: ubuntu-latest - environment: - name: pypi - permissions: - contents: read - id-token: write - steps: - - name: Download distributions artifact - uses: actions/download-artifact@v8 - with: - name: python-distributions - path: dist/ - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - name: Publish package to PyPI - run: uv publish --trusted-publishing always --check-url https://pypi.org/simple dist/*.tar.gz dist/*.whl diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml new file mode 100644 index 0000000..372dd71 --- /dev/null +++ b/.github/workflows/pypi.yaml @@ -0,0 +1,110 @@ +name: Publish to PyPI +"on": + release: + types: [published] + workflow_dispatch: +permissions: + contents: read +jobs: + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install check tooling + run: | + python -m pip install --upgrade pip + python -m pip install twine + - name: Clean dist directory + run: rm -rf dist + - name: Build sdist + run: uv build --no-sources --sdist + - name: Check distributions + run: python -m twine check dist/*.tar.gz + - name: Upload distributions artifact + uses: actions/upload-artifact@v7 + with: + name: python-sdist + path: dist/*.tar.gz + build-wheels: + name: Build wheel (py${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13", "3.14"] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Install check tooling + run: | + python -m pip install --upgrade pip + python -m pip install twine + - name: Clean dist directory + run: rm -rf dist + - name: Build wheel + run: uv build --no-sources --wheel + - name: Check distributions + run: python -m twine check dist/*.whl + - name: Upload wheel artifact + uses: actions/upload-artifact@v7 + with: + name: python-wheel-${{ matrix.python-version }} + path: dist/*.whl + publish: + name: Publish to PyPI + needs: [build-sdist, build-wheels] + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + contents: read + id-token: write + steps: + - name: Download sdist artifact + uses: actions/download-artifact@v8 + with: + name: python-sdist + path: dist/ + - name: Download wheel artifacts + uses: actions/download-artifact@v8 + with: + pattern: python-wheel-* + path: dist/wheels + - name: Prepare publish directory + run: | + mkdir -p dist/publish + cp dist/*.tar.gz dist/publish/ + python - <<'PY' + import hashlib + import shutil + from pathlib import Path + + seen = set() + out = Path("dist/publish") + for wheel in sorted(Path("dist/wheels").rglob("*.whl")): + digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + if digest in seen: + continue + seen.add(digest) + shutil.copy2(wheel, out / wheel.name) + PY + - name: Check distributions + run: python -m twine check dist/publish/* + - name: Set up uv + uses: astral-sh/setup-uv@v7 + - name: Publish package to PyPI + run: uv publish --trusted-publishing always --check-url https://pypi.org/simple dist/publish/* From 4dcd3022ffb8b1bb7a80b16c3104c0bbc596f6e1 Mon Sep 17 00:00:00 2001 From: rbroderi Date: Fri, 10 Apr 2026 20:57:30 -0400 Subject: [PATCH 90/90] fix publish action --- .github/workflows/pypi.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml index 372dd71..9e1f9a9 100644 --- a/.github/workflows/pypi.yaml +++ b/.github/workflows/pypi.yaml @@ -74,6 +74,10 @@ jobs: contents: read id-token: write steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" - name: Download sdist artifact uses: actions/download-artifact@v8 with: @@ -102,6 +106,10 @@ jobs: seen.add(digest) shutil.copy2(wheel, out / wheel.name) PY + - name: Install check tooling + run: | + python -m pip install --upgrade pip + python -m pip install twine - name: Check distributions run: python -m twine check dist/publish/* - name: Set up uv

+ + +