new API
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 2s

This commit is contained in:
2026-04-13 23:02:57 +02:00
parent a44248bc50
commit 4d115e3de4
17 changed files with 965 additions and 275 deletions

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2026 louisdevie Copyright (c) 2026 Louis DEVIE
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 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 associated documentation files (the "Software"), to deal in the Software without restriction, including

View File

@@ -1,7 +1,23 @@
# kpmatch - Path patterns # kpmatch - Path patterns
## Features and limitations
* **Bash-style pathname patterns**
* **Curly braces syntax:** `a{/b/c,bc}` expands into `a/b/c` and `abc`
* **Pattern specificity ranking:** patterns can be sorted to test
a path against the more specific patterns first
* **Pure paths:** This library does not access the filesystem. It does
not check if a path exists and does not differentiate between files
and directories.
* Ranges `[0-9]`, character classes `[:space:]` are
not supported.
* The extended operators `?(...)`, `*(...)`, `+(...)`, `@(...)`
and `!(...)` are not supported.
## Pattern Syntax ## Pattern Syntax
Braced sections `{ , }` are expanded.
Path patterns should always use forward slashes `/` between segments. Path patterns should always use forward slashes `/` between segments.
The actual separator used when matching paths depends on the system. The actual separator used when matching paths depends on the system.
@@ -17,48 +33,3 @@ separator.
Square brackets `[ ]` can match one of the characters between the brackets. Square brackets `[ ]` can match one of the characters between the brackets.
If the first character is an exclamation point `[! ]`, it will match one If the first character is an exclamation point `[! ]`, it will match one
character that does NOT appear between the brackets. character that does NOT appear between the brackets.
Curly braces `{ , }` can match one of the sub-expressions separated by commas.
## API
### Function `kpmatch.kpmatch(path: str | PathLike[str], pattern: str) -> bool`
### Method `kpmatch.Pattern.match(path: str | PathLike[str]) -> bool`
Tests if a path matches a pattern.
<dl>
<dt>Parameters</dt>
<dd>
<strong>path</strong> — A relative or absolute file path.<br/>
<strong>pattern</strong> — A path pattern (see above for the syntax of patterns).
</dd>
<dt>Returns</dt>
<dd>True if the path matches the pattern.</dd>
</dl>
```
function kpmatch.specificity(pattern: str) -> float
property kpmatch.Pattern.specificity: bool
```
Computes the specificity of a pattern. The higher the number, the more
specific it is.
:param pattern: The path pattern to evaluate.
:return: A positive integer.
```python
def compile(pattern: str) -> Pattern
```
Compiles a pattern into a Pattern object that can be reused multiple times.
:param pattern: The path pattern to compile.
```python
def is_valid_pattern(pattern: str) -> bool
```
Verifies the syntax of a pattern.
:param pattern: The path pattern to check.
:return: True if the pattern is well-formed.

20
docs/Makefile Normal file
View File

@@ -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 ?= poetry run 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)

11
docs/api.rst Normal file
View File

@@ -0,0 +1,11 @@
API
===
.. autofunction:: kpmatch.kpmatch
.. autofunction:: kpmatch.compile
.. autofunction:: kpmatch.is_valid_pattern
.. autoclass:: kpmatch.Pattern
:members:

29
docs/conf.py Normal file
View File

@@ -0,0 +1,29 @@
# 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
project = "kpmatch"
copyright = "2026, Louis DEVIE"
author = "Louis DEVIE"
release = "0.1.0"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
]
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "alabaster"
html_static_path = ["_static"]

18
docs/index.rst Normal file
View File

@@ -0,0 +1,18 @@
.. kpmatch documentation master file, created by
sphinx-quickstart on Fri Apr 10 13:31:23 2026.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
kpmatch documentation
=====================
Add your content using ``reStructuredText`` syntax. See the
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
documentation for details.
.. toctree::
:maxdepth: 2
:caption: Contents:
api

35
docs/make.bat Normal file
View File

@@ -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

9
docs/options.rst Normal file
View File

@@ -0,0 +1,9 @@
braces
braces_max_expansion
glob_star
extended_operators
include_child_matches

View File

@@ -4,9 +4,42 @@ kpmatch - Path glob matching functions
import os.path import os.path
from os import PathLike from os import PathLike
from typing import Iterable
from kpmatch.config import Config
from kpmatch.matchers import PathMatcher from kpmatch.matchers import PathMatcher
from kpmatch.parse import _parse_pattern, PatternParsingError from kpmatch.parse import _parse_pattern, PatternParsingError, _expand_braces
def kpmatch(path: str | PathLike[str], pattern: str | Iterable[str], **options) -> bool:
"""
Tests if a path matches a pattern.
:param path: A relative or absolute file path.
:param pattern: A path pattern (see the README for the syntax of patterns).
:return: True if the path matches the pattern.
"""
patterns = compile(pattern, **options)
return any([pattern.match(path) for pattern in patterns])
def compile(pattern: str | Iterable[str], **options) -> list["Pattern"]:
"""
Compiles a pattern into a Pattern object that can be reused multiple times.
:param pattern: The path pattern to compile.
"""
cfg = Config(**options)
if cfg.braces:
patterns = _expand_braces(cfg, pattern)
else:
patterns = [pattern]
return [
Pattern(_parse_pattern(cfg, cfg.path_flavor.normcase(pattern)))
for pattern in patterns
]
class Pattern: class Pattern:
@@ -21,6 +54,12 @@ class Pattern:
self.__first_segment = first_segment self.__first_segment = first_segment
def match(self, path: str): def match(self, path: str):
"""
Tests if a path matches a pattern.
:param path: A relative or absolute file path.
:return: True if the path matches the pattern.
"""
normalised_path = os.path.normcase(path) normalised_path = os.path.normcase(path)
return self.__first_segment.match(normalised_path, 0) return self.__first_segment.match(normalised_path, 0)
@@ -29,13 +68,14 @@ class Pattern:
""" """
The specificity of this pattern. The higher the number, the more The specificity of this pattern. The higher the number, the more
specific it is. specific it is.
:return: A positive integer. :return: A positive integer.
""" """
# if bit 1 is set, there is a fixed segment # if bit 1 is set, there is a fixed segment
# if bit 2 is set, there is fixed-length wildcard (either ?, [] or {}) # if bit 2 is set, there is a fixed-length wildcard (either ? or [])
# if bit 3 is set, there is * wildcard # if bit 3 is set, there is a * wildcard
# if bit 3 is set, there is ** wildcard # if bit 4 is set, there is a ** wildcard
mask = self.__first_segment.generate_wildness_mask() mask = self.__first_segment.generate_wildness_mask()
spec = 0 spec = 0
for n in (1, 2, 4, 8): for n in (1, 2, 4, 8):
@@ -52,45 +92,3 @@ class Pattern:
# add 1 so that it is a little bit more specific # add 1 so that it is a little bit more specific
spec += 1 spec += 1
return spec return spec
def compile(pattern: str) -> Pattern:
"""
Compiles a pattern into a Pattern object that can be reused multiple times.
:param pattern: The path pattern to compile.
"""
normalised_pattern = os.path.normcase(pattern)
return Pattern(_parse_pattern(normalised_pattern))
def kpmatch(path: str | PathLike[str], pattern: str) -> bool:
"""
Tests if a path matches a pattern.
:param path: A relative or absolute file path.
:param pattern: A path pattern (see the README for the syntax of patterns).
:return: True if the path matches the pattern.
"""
return compile(pattern).match(path)
def is_valid_pattern(pattern: str) -> bool:
"""
Verifies the syntax of a pattern.
:param pattern: The path pattern to check.
:return: True if the pattern is well-formed.
"""
try:
compile(pattern)
return True
except PatternParsingError:
return False
def specificity(pattern: str) -> float:
"""
Computes the specificity of a pattern. The higher the number, the more
specific it is.
:param pattern: The path pattern to evaluate.
:return: A positive integer.
"""
return compile(pattern).specificity

55
kpmatch/config.py Normal file
View File

@@ -0,0 +1,55 @@
import os.path
from abc import ABC, abstractmethod
from typing import Any
class Config:
braces: bool
glob_star: bool
extended_operators: bool
include_child_matches: bool
braces_max_expansion: int
path_flavor: "PathFlavor"
def __init__(self, **options):
self.__extract_bool(options, "braces", True)
self.__extract_bool(options, "glob_star", True)
self.__extract_bool(options, "extended_operators", False)
self.__extract_bool(options, "include_child_matches", True)
if "braces_max_expansion" not in options:
self.braces_max_expansion = 10_000
elif isinstance(options["braces_max_expansion"], int):
self.braces_max_expansion = options["braces_max_expansion"]
else:
raise ValueError("the braces_max_expansion option should be an integer")
if "path_flavor" not in options or options["path_flavor"] == "os":
self.path_flavor = OSPathFlavor()
elif options["path_flavor"] == "url":
self.path_flavor = URLPathFlavor()
else:
raise ValueError('the flavor option should be either "os" or "url"')
def __extract_bool(self, options: dict[str, Any], attr: str, default: bool):
if attr not in options:
setattr(self, attr, default)
elif isinstance(options[attr], bool):
setattr(self, attr, options[attr])
else:
raise ValueError(f"the {attr} option should be a boolean value")
class PathFlavor(ABC):
@abstractmethod
def normcase(self, path_or_pattern: str) -> str: ...
class OSPathFlavor(PathFlavor):
def normcase(self, path_or_pattern: str) -> str:
return os.path.normcase(path_or_pattern)
class URLPathFlavor(PathFlavor):
def normcase(self, path_or_pattern: str) -> str:
return path_or_pattern

View File

@@ -1,6 +1,8 @@
import itertools
import os.path import os.path
from typing import Optional, Never from typing import Optional, NoReturn
from kpmatch.config import Config
from kpmatch.matchers import ( from kpmatch.matchers import (
PathMatcher, PathMatcher,
EndOfPath, EndOfPath,
@@ -19,6 +21,102 @@ class PatternParsingError(ValueError):
super().__init__(f'{message} at position {position} in pattern "{pattern}"') super().__init__(f'{message} at position {position} in pattern "{pattern}"')
def _expand_braces(cfg: Config, pattern: str) -> list[str]:
sections = _parse_braces_notation(cfg, pattern)
expanded = [""]
while len(sections) > 0 and len(expanded) < cfg.braces_max_expansion:
section = sections.pop(0)
expanded = [
prefix + alternative for prefix in expanded for alternative in section
]
return expanded
def _parse_braces_notation(cfg: Config, pattern: str) -> list[list[str]]:
# braces_notation <- ( fixed_section (braced_section fixed_section)* )? EOS
i = 0
sections = []
(fixed_section, i) = _parse_fixed_section(pattern, i)
if fixed_section != "":
sections.append([fixed_section])
while i < len(pattern):
(braced_section, i) = _parse_braced_section(pattern, i)
sections.append(braced_section)
(fixed_section, i) = _parse_fixed_section(pattern, i)
if fixed_section != "":
sections.append([fixed_section])
return sections
def _raise_error_in_braced_section(pattern: str, start: int):
if pattern.startswith("{", start):
raise PatternParsingError(
'Character "{" is not allowed inside a braced section',
pattern,
start,
)
def _parse_braced_section(pattern: str, start: int) -> tuple[list[str], int]:
# braced_section = "{" braces_alternative ("," braces_alternative)* "}"
i = start
if not pattern.startswith("{", i):
raise PatternParsingError(f'unexpected character "{pattern[i]}"', pattern, i)
i += 1
ended = False
alternatives = []
_raise_error_in_braced_section(pattern, i)
(alternative, i) = _parse_braces_alternative(pattern, i)
alternatives.append(alternative)
while not ended and i < len(pattern):
_raise_error_in_braced_section(pattern, i)
if pattern.startswith(",", i):
i += 1
_raise_error_in_braced_section(pattern, i)
(alternative, i) = _parse_braces_alternative(pattern, i)
alternatives.append(alternative)
else:
ended = True
if pattern.startswith("}", i):
i += 1
else:
raise PatternParsingError(
'Missing closing bracket "}" to match "{"', pattern, start
)
return alternatives, i
def _is_allowed_in_braces_alternative(pattern: str, start: int) -> bool:
return "{,}".find(pattern[start]) == -1
def _parse_braces_alternative(pattern: str, start: int) -> tuple[str, int]:
# braces_alternative <- (!("{" / "," / "}") .)+
i = start
while i < len(pattern) and _is_allowed_in_braces_alternative(pattern, i):
i += 1
return pattern[start:i], i
def _parse_fixed_section(pattern: str, start: int) -> tuple[str, int]:
# fixed_section <- (!("{" / "}") .)+
i = start
while i < len(pattern) and not pattern.startswith("{", i):
if pattern.startswith("}", i):
raise PatternParsingError(
'Unmatched closing bracket "}"',
pattern,
start,
)
i += 1
return pattern[start:i], i
def _append(a: Optional[PathMatcher], b: PathMatcher) -> PathMatcher: def _append(a: Optional[PathMatcher], b: PathMatcher) -> PathMatcher:
if a is None: if a is None:
return b return b
@@ -27,7 +125,7 @@ def _append(a: Optional[PathMatcher], b: PathMatcher) -> PathMatcher:
return a return a
def _parse_pattern(pattern: str) -> PathMatcher: def _parse_pattern(cfg: Config, pattern: str) -> PathMatcher:
# pattern <- (any_segments / fixed_segment)* # pattern <- (any_segments / fixed_segment)*
i = 0 i = 0
first = None first = None
@@ -60,13 +158,11 @@ def _parse_any_segments(pattern: str, start: int) -> tuple[Optional[PathMatcher]
return matcher, i return matcher, i
def _raise_unexpected_name_part_error(pattern: str, start: int) -> Never: def _raise_unexpected_name_part_error(pattern: str, start: int) -> NoReturn:
if pattern.startswith(os.path.sep, start): if pattern.startswith(os.path.sep, start):
message = "Empty path segment" message = "Empty path segment"
elif pattern.startswith("[", start): elif pattern.startswith("[", start):
message = 'Missing closing bracket "]" to match "["' message = 'Missing closing bracket "]" to match "["'
elif pattern.startswith("{", start):
message = 'Missing closing bracket "}" to match "{"'
else: else:
message = f'unexpected character "{pattern[start]}"' message = f'unexpected character "{pattern[start]}"'
raise PatternParsingError(message, pattern, start) raise PatternParsingError(message, pattern, start)
@@ -95,11 +191,9 @@ def _parse_fixed_segment(pattern: str, start: int) -> tuple[PathMatcher, int]:
def _parse_name_part(pattern: str, start: int) -> tuple[Optional[PathMatcher], int]: def _parse_name_part(pattern: str, start: int) -> tuple[Optional[PathMatcher], int]:
# name_part <- any_name / one_of / simple_name_part / "," # name_part <- any_name / simple_name_part / ","
i = start i = start
(part, i) = _parse_any_name(pattern, i) (part, i) = _parse_any_name(pattern, i)
if part is None:
(part, i) = _parse_one_of(pattern, i)
if part is None: if part is None:
(part, i) = _parse_simple_name_part(pattern, i) (part, i) = _parse_simple_name_part(pattern, i)
if part is None: if part is None:
@@ -119,76 +213,6 @@ def _parse_any_name(pattern: str, start: int) -> tuple[Optional[PathMatcher], in
return matcher, i return matcher, i
def _raise_error_in_one_of(pattern: str, start: int) -> bool:
if pattern.startswith("{", start):
raise PatternParsingError(
'Character "{" is not allowed inside a one-of pattern',
pattern,
start,
)
if pattern.startswith("*", start):
raise PatternParsingError(
'A star "*" wildcard is not allowed inside a one-of pattern',
pattern,
start,
)
def _parse_one_of(pattern: str, start: int) -> tuple[Optional[PathMatcher], int]:
# one_of = "{" one_of_choice ("," one_of_choice)* "}"
i = start
matcher = None
if pattern.startswith("{", i):
i += 1
ended = False
choices = []
_raise_error_in_one_of(pattern, i)
(choice, j) = _parse_one_of_choice(pattern, i)
choices.append((choice, i))
i = j
while not ended and i < len(pattern):
_raise_error_in_one_of(pattern, i)
if pattern.startswith(",", i):
i += 1
_raise_error_in_one_of(pattern, i)
(choice, j) = _parse_one_of_choice(pattern, i)
choices.append((choice, i))
i = j
else:
ended = True
if pattern.startswith("}", i):
i += 1
choice_matchers = []
for choice_matcher, choice_start in choices:
if choice_matcher is None:
raise PatternParsingError(
"Empty choice in a one-of pattern", pattern, choice_start
)
else:
choice_matchers.append(choice_matcher)
matcher = OneOf(choice_matchers)
else:
i = start
return matcher, i
def _parse_one_of_choice(pattern: str, start: int) -> tuple[Optional[PathMatcher], int]:
# one_of_choice = simple_name_part+
i = start
first = None
ended = False
while not ended and i < len(pattern):
(segment, i) = _parse_simple_name_part(pattern, i)
if segment is None:
ended = True
else:
first = _append(first, segment)
return first, i
def _parse_simple_name_part( def _parse_simple_name_part(
pattern: str, start: int pattern: str, start: int
) -> tuple[Optional[PathMatcher], int]: ) -> tuple[Optional[PathMatcher], int]:

547
poetry.lock generated
View File

@@ -1,5 +1,32 @@
# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. # This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]]
name = "alabaster"
version = "1.0.0"
description = "A light, configurable Sphinx theme"
optional = false
python-versions = ">=3.10"
groups = ["docs"]
files = [
{file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"},
{file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
]
[[package]]
name = "babel"
version = "2.18.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"},
{file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"},
]
[package.extras]
dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
[[package]] [[package]]
name = "black" name = "black"
version = "25.11.0" version = "25.11.0"
@@ -52,6 +79,157 @@ d = ["aiohttp (>=3.10)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2026.2.25"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
groups = ["docs"]
files = [
{file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"},
{file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"},
]
[[package]]
name = "charset-normalizer"
version = "3.4.7"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
groups = ["docs"]
files = [
{file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"},
{file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"},
{file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"},
{file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"},
{file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"},
{file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"},
{file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"},
{file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"},
{file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"},
{file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"},
{file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"},
{file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"},
{file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"},
{file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"},
{file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"},
{file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"},
{file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"},
{file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"},
{file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"},
{file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"},
{file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"},
{file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"},
{file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"},
{file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"},
{file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"},
{file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"},
{file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"},
{file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"},
{file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"},
{file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"},
{file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"},
{file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"},
{file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"},
{file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"},
{file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"},
{file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"},
{file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"},
{file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"},
{file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"},
{file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"},
{file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"},
{file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"},
{file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"},
{file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"},
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.3.0" version = "8.3.0"
@@ -73,12 +251,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"] groups = ["dev", "docs"]
markers = "platform_system == \"Windows\""
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
markers = {dev = "platform_system == \"Windows\"", docs = "sys_platform == \"win32\""}
[[package]] [[package]]
name = "coverage" name = "coverage"
@@ -185,6 +363,162 @@ files = [
[package.extras] [package.extras]
toml = ["tomli ; python_full_version <= \"3.11.0a6\""] toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "docutils"
version = "0.21.2"
description = "Docutils -- Python Documentation Utilities"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
{file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
]
[[package]]
name = "idna"
version = "3.11"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.8"
groups = ["docs"]
files = [
{file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"},
{file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "imagesize"
version = "1.5.0"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
groups = ["docs"]
files = [
{file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"},
{file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"},
]
[[package]]
name = "jinja2"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["docs"]
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
version = "3.0.3"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"},
{file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"},
{file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"},
{file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"},
{file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"},
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"},
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"},
{file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"},
{file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"},
{file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"},
{file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"},
{file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"},
{file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"},
{file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"},
{file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"},
{file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"},
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"},
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"},
{file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"},
{file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"},
{file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"},
{file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"},
{file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"},
{file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"},
{file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"},
{file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"},
{file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"},
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"},
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"},
{file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"},
{file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"},
{file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"},
{file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"},
{file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"},
{file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"},
{file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"},
{file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"},
{file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"},
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"},
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"},
{file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"},
{file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"},
{file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"},
{file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"},
{file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"},
{file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"},
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"},
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"},
{file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"},
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"},
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"},
{file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"},
{file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"},
{file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"},
{file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"},
{file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"},
{file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"},
{file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"},
{file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"},
{file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"},
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"},
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"},
{file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"},
{file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"},
{file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"},
{file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"},
{file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"},
{file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"},
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"},
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"},
{file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"},
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"},
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"},
{file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"},
{file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"},
{file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"},
{file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"},
{file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"},
{file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"},
{file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"},
{file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"},
{file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"},
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"},
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"},
{file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"},
{file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"},
{file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"},
{file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"},
{file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
]
[[package]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.1.0" version = "1.1.0"
@@ -203,7 +537,7 @@ version = "25.0"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev", "docs"]
files = [ files = [
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
@@ -238,6 +572,21 @@ docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"]
type = ["mypy (>=1.18.2)"] type = ["mypy (>=1.18.2)"]
[[package]]
name = "pygments"
version = "2.20.0"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
{file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "pytokens" name = "pytokens"
version = "0.3.0" version = "0.3.0"
@@ -253,13 +602,183 @@ files = [
[package.extras] [package.extras]
dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
[[package]]
name = "requests"
version = "2.33.1"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.10"
groups = ["docs"]
files = [
{file = "requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a"},
{file = "requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517"},
]
[package.dependencies]
certifi = ">=2023.5.7"
charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.26,<3"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
[[package]]
name = "snowballstemmer"
version = "3.0.1"
description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*"
groups = ["docs"]
files = [
{file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"},
{file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"},
]
[[package]]
name = "sphinx"
version = "8.1.3"
description = "Python documentation generator"
optional = false
python-versions = ">=3.10"
groups = ["docs"]
files = [
{file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"},
{file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"},
]
[package.dependencies]
alabaster = ">=0.7.14"
babel = ">=2.13"
colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
docutils = ">=0.20,<0.22"
imagesize = ">=1.3"
Jinja2 = ">=3.1"
packaging = ">=23.0"
Pygments = ">=2.17"
requests = ">=2.30.0"
snowballstemmer = ">=2.2"
sphinxcontrib-applehelp = ">=1.0.7"
sphinxcontrib-devhelp = ">=1.0.6"
sphinxcontrib-htmlhelp = ">=2.0.6"
sphinxcontrib-jsmath = ">=1.0.1"
sphinxcontrib-qthelp = ">=1.0.6"
sphinxcontrib-serializinghtml = ">=1.1.9"
tomli = {version = ">=2", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["sphinxcontrib-websupport"]
lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"]
test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
[[package]]
name = "sphinxcontrib-applehelp"
version = "2.0.0"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-devhelp"
version = "2.0.0"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-htmlhelp"
version = "2.1.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["html5lib", "pytest"]
[[package]]
name = "sphinxcontrib-jsmath"
version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
optional = false
python-versions = ">=3.5"
groups = ["docs"]
files = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
]
[package.extras]
test = ["flake8", "mypy", "pytest"]
[[package]]
name = "sphinxcontrib-qthelp"
version = "2.0.0"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["defusedxml (>=0.7.1)", "pytest"]
[[package]]
name = "sphinxcontrib-serializinghtml"
version = "2.0.0"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
]
[package.extras]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.3.0" version = "2.3.0"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev", "docs"]
markers = "python_version == \"3.10\"" markers = "python_version == \"3.10\""
files = [ files = [
{file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"},
@@ -319,7 +838,25 @@ files = [
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
] ]
[[package]]
name = "urllib3"
version = "2.6.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["docs"]
files = [
{file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"},
{file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"},
]
[package.extras]
brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10" python-versions = ">=3.10"
content-hash = "2afe710a5499b666d6de8a1b2f1427995d8e5597cbf0fa7caeff697e9a391d00" content-hash = "5e5f8ca1bd36635f5011e71800487e4fb3ff5c462fc94925deae63b2f73bacc7"

View File

@@ -19,3 +19,5 @@ build-backend = "poetry.core.masonry.api"
black = "^25.11.0" black = "^25.11.0"
coverage = "^7.11.3" coverage = "^7.11.3"
[tool.poetry.group.docs.dependencies]
sphinx = "^8.1.3"

View File

@@ -5,7 +5,7 @@ import kpmatch
class MatchTests(TestCase): class MatchTests(TestCase):
def test_match_empty(self): def test_match_empty(self):
pattern = kpmatch.compile("") (pattern,) = kpmatch.compile("")
self.assertTrue(pattern.match("")) self.assertTrue(pattern.match(""))
self.assertFalse(pattern.match("index.html")) self.assertFalse(pattern.match("index.html"))
self.assertFalse(pattern.match("about")) self.assertFalse(pattern.match("about"))
@@ -13,7 +13,7 @@ class MatchTests(TestCase):
self.assertFalse(pattern.match("content/deep/deeper/file.txt")) self.assertFalse(pattern.match("content/deep/deeper/file.txt"))
def test_match_any(self): def test_match_any(self):
pattern = kpmatch.compile("**") (pattern,) = kpmatch.compile("**")
self.assertTrue(pattern.match("")) self.assertTrue(pattern.match(""))
self.assertTrue(pattern.match("index.html")) self.assertTrue(pattern.match("index.html"))
self.assertTrue(pattern.match("about")) self.assertTrue(pattern.match("about"))
@@ -21,7 +21,7 @@ class MatchTests(TestCase):
self.assertTrue(pattern.match("content/deep/deeper/file.txt")) self.assertTrue(pattern.match("content/deep/deeper/file.txt"))
def test_match_any_name(self): def test_match_any_name(self):
pattern = kpmatch.compile("*.html") (pattern,) = kpmatch.compile("*.html")
self.assertTrue(pattern.match("index.html")) self.assertTrue(pattern.match("index.html"))
self.assertTrue(pattern.match("about.html")) self.assertTrue(pattern.match("about.html"))
self.assertFalse(pattern.match("about")) self.assertFalse(pattern.match("about"))
@@ -29,7 +29,7 @@ class MatchTests(TestCase):
self.assertFalse(pattern.match("content/deep/deeper/file.txt")) self.assertFalse(pattern.match("content/deep/deeper/file.txt"))
def test_match_any_name_anywhere(self): def test_match_any_name_anywhere(self):
pattern = kpmatch.compile("**/*.html") (pattern,) = kpmatch.compile("**/*.html")
self.assertTrue(pattern.match("index.html")) self.assertTrue(pattern.match("index.html"))
self.assertTrue(pattern.match("about.html")) self.assertTrue(pattern.match("about.html"))
self.assertFalse(pattern.match("about")) self.assertFalse(pattern.match("about"))
@@ -38,36 +38,28 @@ class MatchTests(TestCase):
self.assertFalse(pattern.match("content/deep/deeper/file.txt")) self.assertFalse(pattern.match("content/deep/deeper/file.txt"))
def test_match_any_directory_name(self): def test_match_any_directory_name(self):
pattern = kpmatch.compile("packages/*/package.json") (pattern,) = kpmatch.compile("packages/*/package.json")
self.assertFalse(pattern.match("packages/package.json")) self.assertFalse(pattern.match("packages/package.json"))
self.assertTrue(pattern.match("packages/a/package.json")) self.assertTrue(pattern.match("packages/a/package.json"))
self.assertTrue(pattern.match("packages/b/package.json")) self.assertTrue(pattern.match("packages/b/package.json"))
self.assertFalse(pattern.match("packages/a/b/package.json")) self.assertFalse(pattern.match("packages/a/b/package.json"))
def test_match_one_of(self):
pattern = kpmatch.compile("*.{png,jpg,jpeg,webp}")
self.assertTrue(pattern.match("image.png"))
self.assertTrue(pattern.match("image.jpg"))
self.assertTrue(pattern.match("image.jpeg"))
self.assertTrue(pattern.match("image.webp"))
self.assertFalse(pattern.match("image.svg"))
def test_match_character_set(self): def test_match_character_set(self):
pattern = kpmatch.compile("*.[jt]s") (pattern,) = kpmatch.compile("*.[jt]s")
self.assertTrue(pattern.match("file.js")) self.assertTrue(pattern.match("file.js"))
self.assertTrue(pattern.match("file.ts")) self.assertTrue(pattern.match("file.ts"))
self.assertFalse(pattern.match("file.rs")) self.assertFalse(pattern.match("file.rs"))
self.assertFalse(pattern.match("file.cs")) self.assertFalse(pattern.match("file.cs"))
def test_match_negative_character_set(self): def test_match_negative_character_set(self):
pattern = kpmatch.compile("*.[!jt]s") (pattern,) = kpmatch.compile("*.[!jt]s")
self.assertFalse(pattern.match("file.js")) self.assertFalse(pattern.match("file.js"))
self.assertFalse(pattern.match("file.ts")) self.assertFalse(pattern.match("file.ts"))
self.assertTrue(pattern.match("file.rs")) self.assertTrue(pattern.match("file.rs"))
self.assertTrue(pattern.match("file.cs")) self.assertTrue(pattern.match("file.cs"))
def test_match_any_character(self): def test_match_any_character(self):
pattern = kpmatch.compile("*.?s") (pattern,) = kpmatch.compile("*.?s")
self.assertTrue(pattern.match("file.js")) self.assertTrue(pattern.match("file.js"))
self.assertTrue(pattern.match("file.ts")) self.assertTrue(pattern.match("file.ts"))
self.assertTrue(pattern.match("file.rs")) self.assertTrue(pattern.match("file.rs"))

View File

@@ -1,9 +1,9 @@
import os.path import os.path
from unittest import TestCase from unittest import TestCase
from kpmatch import PatternParsingError
import kpmatch.matchers as m import kpmatch.matchers as m
import kpmatch.parse as p import kpmatch.parse as p
from kpmatch.config import Config
def join_matchers(first: m.PathMatcher, *others: m.PathMatcher) -> m.PathMatcher: def join_matchers(first: m.PathMatcher, *others: m.PathMatcher) -> m.PathMatcher:
@@ -13,10 +13,59 @@ def join_matchers(first: m.PathMatcher, *others: m.PathMatcher) -> m.PathMatcher
class ParseTests(TestCase): class ParseTests(TestCase):
def test_expand_braces(self):
cfg = Config()
self.assertEqual(
[
"ab/**/*.def",
"ab/ghi",
"a?c/**/*.def",
"a?c/ghi",
],
p._expand_braces(cfg, "a{b,?c}/{**/*.def,ghi}"),
)
def test_parse_braces_notation(self):
# braces_notation <- ( fixed_section (braced_section fixed_section)* )? EOS
cfg = Config()
self.assertEqual(p._parse_braces_notation(cfg, ""), [])
self.assertEqual(
p._parse_braces_notation(cfg, "a{b,?c}/{**/*.def,ghi}"),
[["a"], ["b", "?c"], ["/"], ["**/*.def", "ghi"]],
)
def test_parse_braced_section(self):
# braced_section = "{" braces_alternative ("," braces_alternative)* "}"
self.assertTupleEqual(
p._parse_braced_section("{abc,def}", 0), (["abc", "def"], 9)
)
self.assertTupleEqual(p._parse_braced_section("{abc}", 0), (["abc"], 5))
self.assertTupleEqual(
p._parse_braced_section("{a,b,c,}", 0), (["a", "b", "c", ""], 8)
)
self.assertRaises(p.PatternParsingError, p._parse_braced_section, "abc,def}", 0)
self.assertRaises(p.PatternParsingError, p._parse_braced_section, "{abc,def", 0)
self.assertRaises(
p.PatternParsingError, p._parse_braced_section, "{abc{def}", 0
)
def test_parse_braces_alternative(self):
# braces_alternative <- (!("{" / "," / "}") .)+
self.assertTupleEqual(p._parse_braces_alternative("ab{c,d}", 0), ("ab", 2))
self.assertTupleEqual(p._parse_braces_alternative("ab{c,d}", 3), ("c", 4))
self.assertTupleEqual(p._parse_braces_alternative("ab{c,d}", 5), ("d", 6))
def test_parse_fixed_section(self):
# fixed_section <- (!("{" / "}") .)+
self.assertTupleEqual(p._parse_fixed_section("", 0), ("", 0))
self.assertTupleEqual(p._parse_fixed_section("ab{c,d}", 0), ("ab", 2))
self.assertRaises(p.PatternParsingError, p._parse_fixed_section, "c,d}", 0)
def test_parse_pattern(self): def test_parse_pattern(self):
cfg = Config()
# pattern <- (any_segments / fixed_segment)* # pattern <- (any_segments / fixed_segment)*
self.assertEqual( self.assertEqual(
p._parse_pattern("?a[bc]/**/*.{def,ghi}"), p._parse_pattern(cfg, "?a[bc]/**/*.def"),
join_matchers( join_matchers(
m.AnyCharacter(), m.AnyCharacter(),
m.FixedName("a"), m.FixedName("a"),
@@ -25,7 +74,7 @@ class ParseTests(TestCase):
m.AnySegments(), m.AnySegments(),
m.AnyName(), m.AnyName(),
m.FixedName("."), m.FixedName("."),
m.OneOf((m.FixedName("def"), m.FixedName("ghi"))), m.FixedName("def"),
m.EndOfSegment(), m.EndOfSegment(),
m.EndOfPath(), m.EndOfPath(),
), ),
@@ -40,7 +89,7 @@ class ParseTests(TestCase):
def test_parse_fixed_segment(self): def test_parse_fixed_segment(self):
# fixed_segment <- name_part+ (SEP / EOS) # fixed_segment <- name_part+ (SEP / EOS)
self.assertTupleEqual( self.assertTupleEqual(
p._parse_fixed_segment("?a[bc]/*.{def,ghi}", 0), p._parse_fixed_segment("?a[bc]/*.def", 0),
( (
join_matchers( join_matchers(
m.AnyCharacter(), m.AnyCharacter(),
@@ -52,15 +101,14 @@ class ParseTests(TestCase):
), ),
) )
self.assertTupleEqual( self.assertTupleEqual(
p._parse_fixed_segment("?a[bc]/*.{def,ghi}", 7), p._parse_fixed_segment("?a[bc]/*.def", 7),
( (
join_matchers( join_matchers(
m.AnyName(), m.AnyName(),
m.FixedName("."), m.FixedName(".def"),
m.OneOf((m.FixedName("def"), m.FixedName("ghi"))),
m.EndOfSegment(), m.EndOfSegment(),
), ),
18, 12,
), ),
) )
@@ -82,8 +130,8 @@ class ParseTests(TestCase):
p._parse_name_part("?a[bc],*{def,ghi}", 7), (m.AnyName(), 8) p._parse_name_part("?a[bc],*{def,ghi}", 7), (m.AnyName(), 8)
) )
self.assertTupleEqual( self.assertTupleEqual(
p._parse_name_part("?a[bc],*{def,ghi}", 8), p._parse_name_part("?a[bc],*def", 8),
(m.OneOf((m.FixedName("def"), m.FixedName("ghi"))), 17), (m.FixedName("def"), 17),
) )
def test_parse_any_name(self): def test_parse_any_name(self):
@@ -92,56 +140,6 @@ class ParseTests(TestCase):
self.assertTupleEqual(p._parse_any_name("a*", 1), (m.AnyName(), 2)) self.assertTupleEqual(p._parse_any_name("a*", 1), (m.AnyName(), 2))
self.assertTupleEqual(p._parse_any_name("a***a", 1), (m.AnyName(), 4)) self.assertTupleEqual(p._parse_any_name("a***a", 1), (m.AnyName(), 4))
def test_parse_one_of(self):
# one_of = "{" simple_name_part+ ("," simple_name_part+)* "}"
self.assertTupleEqual(p._parse_one_of("", 0), (None, 0))
self.assertTupleEqual(p._parse_one_of("abc,def}", 0), (None, 0))
self.assertTupleEqual(p._parse_one_of("{abc,def", 0), (None, 0))
self.assertTupleEqual(
p._parse_one_of("{abc,def}", 0),
(m.OneOf((m.FixedName("abc"), m.FixedName("def"))), 9),
)
self.assertTupleEqual(
p._parse_one_of("{a[bc],d?f,ghi}", 0),
(
m.OneOf(
(
join_matchers(m.FixedName("a"), m.CharacterSet("bc", False)),
join_matchers(
m.FixedName("d"), m.AnyCharacter(), m.FixedName("f")
),
m.FixedName("ghi"),
)
),
15,
),
)
self.assertRaises(PatternParsingError, p._parse_one_of, "{abc{def}", 0)
self.assertTupleEqual(
p._parse_one_of("{abc{def}", 4), (m.OneOf((m.FixedName("def"),)), 9)
)
self.assertTupleEqual(
p._parse_one_of("{abc}def}", 0), (m.OneOf((m.FixedName("abc"),)), 5)
)
self.assertTupleEqual(
p._parse_one_of(os.path.join("{abc", "def}"), 0),
(None, 0),
)
self.assertTupleEqual(p._parse_one_of("{,def", 0), (None, 0))
self.assertRaises(PatternParsingError, p._parse_one_of, "{,def}", 0)
def test_parse_one_of_choice(self):
# one_of_choice = simple_name_part+
self.assertTupleEqual(
p._parse_one_of_choice("?a[bc],def", 0),
(
join_matchers(
m.AnyCharacter(), m.FixedName("a"), m.CharacterSet("bc", False)
),
6,
),
)
def test_parse_simple_name_part(self): def test_parse_simple_name_part(self):
# simple_name_part = any_character / character_set / fixed_name # simple_name_part = any_character / character_set / fixed_name
self.assertTupleEqual( self.assertTupleEqual(
@@ -169,11 +167,11 @@ class ParseTests(TestCase):
p._parse_character_set("[!abcdef]", 0), (m.CharacterSet("abcdef", True), 9) p._parse_character_set("[!abcdef]", 0), (m.CharacterSet("abcdef", True), 9)
) )
self.assertTupleEqual(p._parse_character_set("[abcdef]", 3), (None, 3)) self.assertTupleEqual(p._parse_character_set("[abcdef]", 3), (None, 3))
self.assertRaises(PatternParsingError, p._parse_character_set, "[abc[def]", 0) self.assertRaises(p.PatternParsingError, p._parse_character_set, "[abc[def]", 0)
self.assertTupleEqual( self.assertTupleEqual(
p._parse_character_set("[abc[def]", 4), (m.CharacterSet("def", False), 9) p._parse_character_set("[abc[def]", 4), (m.CharacterSet("def", False), 9)
) )
self.assertRaises(PatternParsingError, p._parse_character_set, "[abc!def]", 0) self.assertRaises(p.PatternParsingError, p._parse_character_set, "[abc!def]", 0)
self.assertTupleEqual( self.assertTupleEqual(
p._parse_character_set("[abc]def]", 0), (m.CharacterSet("abc", False), 5) p._parse_character_set("[abc]def]", 0), (m.CharacterSet("abc", False), 5)
) )

View File

@@ -9,25 +9,20 @@ from kpmatch import PatternParsingError
class ParseTests(TestCase): class ParseTests(TestCase):
def test_compile(self): def test_compile(self):
pattern = kpmatch.compile("*.txt") (pattern,) = kpmatch.compile("*.txt")
self.assertIsInstance(pattern, kpmatch.Pattern) self.assertIsInstance(pattern, kpmatch.Pattern)
self.assertTrue(pattern.match("file.txt")) self.assertTrue(pattern.match("file.txt"))
self.assertEqual(pattern.specificity, 12)
patterns = kpmatch.compile("*.{png,jpg,jpeg,webp}")
self.assertEqual(4, len(patterns))
self.assertTrue(any([pattern.match("file.jpeg") for pattern in patterns]))
self.assertTrue(any([pattern.match("file.png") for pattern in patterns]))
self.assertFalse(any([pattern.match("file.txt") for pattern in patterns]))
def test_kpmatch(self): def test_kpmatch(self):
self.assertTrue(kpmatch.kpmatch("file.txt", "*.txt")) self.assertTrue(kpmatch.kpmatch("file.txt", "*.txt"))
self.assertFalse(kpmatch.kpmatch("file.jpg", "*.txt")) self.assertFalse(kpmatch.kpmatch("file.jpg", "*.txt"))
def test_is_valid_pattern(self):
self.assertTrue(kpmatch.is_valid_pattern("*.txt"))
self.assertFalse(kpmatch.is_valid_pattern("[.txt"))
def test_specificity(self):
self.assertEqual(kpmatch.specificity(""), 0)
self.assertEqual(kpmatch.specificity("**"), 1)
self.assertEqual(kpmatch.specificity("*.txt"), 12)
self.assertEqual(kpmatch.specificity("file.txt"), 15)
def assertRaisesPPE(self, error_message: str, position: int, pattern: str): def assertRaisesPPE(self, error_message: str, position: int, pattern: str):
with self.assertRaises(PatternParsingError) as cm: with self.assertRaises(PatternParsingError) as cm:
kpmatch.compile(pattern) kpmatch.compile(pattern)
@@ -49,12 +44,5 @@ class ParseTests(TestCase):
self.assertRaisesPPE('Missing closing bracket "}" to match "{"', 3, "abc{def") self.assertRaisesPPE('Missing closing bracket "}" to match "{"', 3, "abc{def")
self.assertRaisesPPE( self.assertRaisesPPE(
'Character "{" is not allowed inside a one-of pattern', 6, "abc{de{f}" 'Character "{" is not allowed inside a braced section', 6, "abc{de{f}"
) )
self.assertRaisesPPE(
'A star "*" wildcard is not allowed inside a one-of pattern',
8,
"abc{def,*}",
)
self.assertRaisesPPE("Empty choice in a one-of pattern", 8, "abc{def,}")
self.assertRaisesPPE("Empty choice in a one-of pattern", 4, "abc{,def}")

View File

@@ -1,6 +1,10 @@
from unittest import TestCase from unittest import TestCase
from kpmatch import specificity from kpmatch import compile
def specificity(pattern: str) -> int:
return compile(pattern)[0].specificity
class SpecificityTests(TestCase): class SpecificityTests(TestCase):
@@ -10,7 +14,6 @@ class SpecificityTests(TestCase):
self.assertGreater(specificity("**/a/file.txt"), specificity("**/a/*.txt")) self.assertGreater(specificity("**/a/file.txt"), specificity("**/a/*.txt"))
self.assertGreater(specificity("file.txt"), specificity("*.txt")) self.assertGreater(specificity("file.txt"), specificity("*.txt"))
self.assertGreater(specificity("*.txt"), specificity("*")) self.assertGreater(specificity("*.txt"), specificity("*"))
self.assertGreater(specificity("file.{txt,html}"), specificity("*.txt"))
self.assertGreater(specificity("image_???.png"), specificity("image_*.png")) self.assertGreater(specificity("image_???.png"), specificity("image_*.png"))
self.assertGreater(specificity("image.png"), specificity("image_???.png")) self.assertGreater(specificity("image.png"), specificity("image_???.png"))