├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bbcode.py ├── docs ├── formatters.md ├── index.md └── tags.md ├── mkdocs.yml ├── pyproject.toml ├── tests.py └── uv.lock /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | UV_SYSTEM_PYTHON: 1 7 | UV_PYTHON_DOWNLOADS: never 8 | UV_PYTHON_PREFERENCE: only-system 9 | 10 | jobs: 11 | checks: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: astral-sh/ruff-action@v1 16 | 17 | test: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Setup uv 29 | uses: astral-sh/setup-uv@v3 30 | - name: Run Tests 31 | run: uv run --no-dev -m unittest 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | __pycache__ 4 | .DS_Store 5 | /dist 6 | /build 7 | /pip-wheel-metadata 8 | .vscode 9 | .nova 10 | .coverage 11 | /htmlcov 12 | /site 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.2.0 2 | 3 | * Test on Python 3.9 - 3.13 4 | * Install a proper `bbcode` script in addition to being able to run `python -m bbcode` 5 | 6 | 7 | ### 1.1.0 8 | 9 | * Now using a `CaseInsensitiveDict` to store tag options, so they retain the source case, but can be accessed case-insensitively. 10 | * Dropped support for Python 2.6, tested against Python 3.8. 11 | * Improved code coverage, formatted with black, and cleaned up Flake8 warnings. 12 | 13 | --- 14 | 15 | ### 1.0.33 16 | 17 | * Added a `max_tag_depth` argument to the `Parser` class, defaulting to Python's recursion limit (Thanks, Lanny). 18 | 19 | ### 1.0.32 20 | 21 | * List items `[*]` only render inside of `[list]` tags, to avoid producing invalid HTML. 22 | * Switched to use `from __future__ import unicode_literals` so things like `url_template` can handle unicode replacements. 23 | * Test on Python 3.7. 24 | 25 | ### 1.0.28 26 | 27 | * Added a `default_context` argument to the `Parser` class. 28 | * Added a `url_template` argument to the `Parser` class, allowing customization of the default linker (see #19 and #28). 29 | 30 | ### 1.0.27 31 | 32 | * Set built-in `code` tag to `replace_cosmetic=False`. 33 | 34 | ### 1.0.26 35 | 36 | * Allow overriding parser's `replace_html`, `replace_links`, and `replace_cosmetic` on `format` calls. 37 | 38 | ### 1.0.25 39 | 40 | * Allow escaping quotes in tag options using backslash, i.e. `[quote='Sinéad O\'Connor']`. 41 | 42 | ### 1.0.11 43 | 44 | * TagOptions now defaults to strip=False (see #7). list and quote tags have been set to strip=True, as they are typically block-level elements anyway. 45 | * A new "drop_unrecognized" option was added to the Parser. If set to True, unrecognized tags will be dropped (the default is to render them as regular text). 46 | 47 | ### 1.0.10 48 | 49 | * Small bugfix concerning render_embedded (see #6). 50 | 51 | ### 1.0.9 52 | 53 | * Escape quotes correctly to prevent XSS (see #4). 54 | 55 | ### 1.0.8 56 | 57 | * Fixed a bug where escaping and cosmetic replacements were incorrectly performed on URLs (f6e0c11). 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Dan Watson. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | **Latest Package** 5 | http://pypi.python.org/pypi/bbcode 6 | 7 | **Source Code** 8 | https://github.com/dcwatson/bbcode 9 | 10 | **Documentation** 11 | https://dcwatson.github.io/bbcode/ 12 | 13 | [![CI Status](https://github.com/dcwatson/bbcode/workflows/CI/badge.svg)](https://github.com/dcwatson/bbcode/actions) 14 | 15 | 16 | Installation 17 | ============ 18 | 19 | The easiest way to install the bbcode module is with pip, e.g.: 20 | 21 | pip install bbcode 22 | 23 | 24 | Requirements 25 | ============ 26 | 27 | Python 3.9+ 28 | 29 | 30 | Basic Usage 31 | =========== 32 | 33 | ```python 34 | # Using the default parser. 35 | import bbcode 36 | html = bbcode.render_html(text) 37 | 38 | # Installing simple formatters. 39 | parser = bbcode.Parser() 40 | parser.add_simple_formatter('hr', '
', standalone=True) 41 | parser.add_simple_formatter('sub', '%(value)s') 42 | parser.add_simple_formatter('sup', '%(value)s') 43 | 44 | # A custom render function. 45 | def render_color(tag_name, value, options, parent, context): 46 | return '%s' % (tag_name, value) 47 | 48 | # Installing advanced formatters. 49 | for color in ('red', 'blue', 'green', 'yellow', 'black', 'white'): 50 | parser.add_formatter(color, render_color) 51 | 52 | # Calling format with context. 53 | html = parser.format(text, somevar='somevalue') 54 | ``` 55 | -------------------------------------------------------------------------------- /bbcode.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from collections import OrderedDict 4 | from collections.abc import Mapping, MutableMapping 5 | 6 | __version__ = "1.2.0" 7 | __version_info__ = tuple(int(num) for num in __version__.split(".")) 8 | 9 | 10 | # Adapted from http://daringfireball.net/2010/07/improved_regex_for_matching_urls 11 | # Changed to only support one level of parentheses, since it was failing 12 | # catastrophically on some URLs. 13 | # See http://www.regular-expressions.info/catastrophic.html 14 | _url_re = re.compile( 15 | r"(?im)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)" 16 | r'(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^\s`!()\[\]{};:\'".,<>?]))' 17 | ) 18 | 19 | # For the URL tag, try to be smart about when to append a missing http://. If the given 20 | # link looks like a domain, add a http:// in front of it, otherwise leave it alone 21 | # (since it may be a relative path, a filename, etc). 22 | _domain_re = re.compile( 23 | r"(?im)(?:www\d{0,3}[.]|[a-z0-9.\-]+[.](?:com|net|org|edu|biz|gov|mil|info|io|name|me|tv|us|uk|mobi))" 24 | ) 25 | 26 | 27 | # Taken from https://github.com/psf/requests/blob/eedd67462819f8dbf8c1c32e77f9070606605231/requests/structures.py#L15 28 | class CaseInsensitiveDict(MutableMapping): 29 | def __init__(self, data=None, **kwargs): 30 | self._store = OrderedDict() 31 | if data is None: 32 | data = {} 33 | self.update(data, **kwargs) 34 | 35 | def __setitem__(self, key, value): 36 | # Use the lowercased key for lookups, but store the actual 37 | # key alongside the value. 38 | self._store[key.lower()] = (key, value) 39 | 40 | def __getitem__(self, key): 41 | return self._store[key.lower()][1] 42 | 43 | def __delitem__(self, key): 44 | del self._store[key.lower()] 45 | 46 | def __iter__(self): 47 | return (casedkey for casedkey, mappedvalue in self._store.values()) 48 | 49 | def __len__(self): 50 | return len(self._store) 51 | 52 | def lower_items(self): 53 | """Like iteritems(), but with all lowercase keys.""" 54 | return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) 55 | 56 | def __eq__(self, other): 57 | if isinstance(other, Mapping): 58 | other = CaseInsensitiveDict(other) 59 | else: 60 | return NotImplemented 61 | # Compare insensitively 62 | return dict(self.lower_items()) == dict(other.lower_items()) 63 | 64 | # Copy is required 65 | def copy(self): 66 | return CaseInsensitiveDict(self._store.values()) 67 | 68 | def __repr__(self): 69 | return str(dict(self.items())) 70 | 71 | 72 | class TagOptions(object): 73 | # The name of the tag, all lowercase. 74 | tag_name = None 75 | 76 | # True if a newline should automatically close this tag. 77 | newline_closes = False 78 | 79 | # True if another start of the same tag should automatically close this tag. 80 | same_tag_closes = False 81 | 82 | # True if this tag does not have a closing tag. 83 | standalone = False 84 | 85 | # True if tags should be rendered inside this tag. 86 | render_embedded = True 87 | 88 | # True if newlines should be converted to markup. 89 | transform_newlines = True 90 | 91 | # True if HTML characters (<, >, and &) should be escaped inside this tag. 92 | escape_html = True 93 | 94 | # True if URLs should be replaced with link markup inside this tag. 95 | replace_links = True 96 | 97 | # True if cosmetic replacements (elipses, dashes, etc.) should be performed inside 98 | # this tag. 99 | replace_cosmetic = True 100 | 101 | # True if leading and trailing whitespace should be stripped inside this tag. 102 | strip = False 103 | 104 | # True if this tag should swallow the first trailing newline (i.e. for block 105 | # elements). 106 | swallow_trailing_newline = False 107 | 108 | def __init__(self, tag_name, **kwargs): 109 | self.tag_name = tag_name 110 | for attr, value in list(kwargs.items()): 111 | setattr(self, attr, bool(value)) 112 | 113 | 114 | class Parser(object): 115 | TOKEN_TAG_START = 1 116 | TOKEN_TAG_END = 2 117 | TOKEN_NEWLINE = 3 118 | TOKEN_DATA = 4 119 | 120 | REPLACE_ESCAPE = ( 121 | ("&", "&"), 122 | ("<", "<"), 123 | (">", ">"), 124 | ('"', """), 125 | ("'", "'"), 126 | ) 127 | 128 | REPLACE_COSMETIC = ( 129 | ("---", "—"), 130 | ("--", "–"), 131 | ("...", "…"), 132 | ("(c)", "©"), 133 | ("(reg)", "®"), 134 | ("(tm)", "™"), 135 | ) 136 | 137 | def __init__( 138 | self, 139 | newline="
", 140 | install_defaults=True, 141 | escape_html=True, 142 | replace_links=True, 143 | replace_cosmetic=True, 144 | tag_opener="[", 145 | tag_closer="]", 146 | linker=None, 147 | linker_takes_context=False, 148 | drop_unrecognized=False, 149 | default_context=None, 150 | max_tag_depth=None, 151 | url_template='{text}', 152 | ): 153 | self.tag_opener = tag_opener 154 | self.tag_closer = tag_closer 155 | self.newline = newline 156 | self.recognized_tags = {} 157 | self.drop_unrecognized = drop_unrecognized 158 | self.escape_html = escape_html 159 | self.replace_cosmetic = replace_cosmetic 160 | self.replace_links = replace_links 161 | self.linker = linker 162 | self.linker_takes_context = linker_takes_context 163 | self.max_tag_depth = max_tag_depth or sys.getrecursionlimit() 164 | self.url_template = url_template 165 | self.default_context = default_context or {} 166 | if install_defaults: 167 | self.install_default_formatters() 168 | 169 | def add_formatter(self, tag_name, render_func, **kwargs): 170 | """ 171 | Installs a render function for the specified tag name. The render function 172 | should have the following signature: 173 | 174 | def render(tag_name, value, options, parent, context) 175 | 176 | The arguments are as follows: 177 | 178 | tag_name 179 | The name of the tag being rendered. 180 | value 181 | The context between start and end tags, or None for standalone tags. 182 | Whether this has been rendered depends on render_embedded tag option. 183 | options 184 | A dictionary of options specified on the opening tag. 185 | parent 186 | The parent TagOptions, if the tag is being rendered inside another tag, 187 | otherwise None. 188 | context 189 | The keyword argument dictionary passed into the format call. 190 | """ 191 | options = TagOptions(tag_name.strip().lower(), **kwargs) 192 | self.recognized_tags[options.tag_name] = (render_func, options) 193 | 194 | def add_simple_formatter(self, tag_name, format_string, **kwargs): 195 | """ 196 | Installs a formatter that takes the tag options dictionary, puts a value key 197 | in it, and uses it as a format dictionary to the given format string. 198 | """ 199 | 200 | def _render(name, value, options, parent, context): 201 | fmt = {} 202 | if options: 203 | fmt.update(options) 204 | fmt.update({"value": value}) 205 | return format_string % fmt 206 | 207 | self.add_formatter(tag_name, _render, **kwargs) 208 | 209 | def install_default_formatters(self): 210 | """ 211 | Installs default formatters for the following tags: 212 | 213 | b, i, u, s, list (and *), quote, code, center, color, url 214 | """ 215 | self.add_simple_formatter("b", "%(value)s") 216 | self.add_simple_formatter("i", "%(value)s") 217 | self.add_simple_formatter("u", "%(value)s") 218 | self.add_simple_formatter("s", "%(value)s") 219 | self.add_simple_formatter("hr", "
", standalone=True) 220 | self.add_simple_formatter("sub", "%(value)s") 221 | self.add_simple_formatter("sup", "%(value)s") 222 | 223 | def _render_list(name, value, options, parent, context): 224 | list_type = options["list"] if (options and "list" in options) else "*" 225 | css_opts = { 226 | "1": "decimal", 227 | "01": "decimal-leading-zero", 228 | "a": "lower-alpha", 229 | "A": "upper-alpha", 230 | "i": "lower-roman", 231 | "I": "upper-roman", 232 | } 233 | tag = "ol" if list_type in css_opts else "ul" 234 | css = ( 235 | ' style="list-style-type:%s;"' % css_opts[list_type] 236 | if list_type in css_opts 237 | else "" 238 | ) 239 | return "<%s%s>%s" % (tag, css, value, tag) 240 | 241 | self.add_formatter( 242 | "list", 243 | _render_list, 244 | transform_newlines=False, 245 | strip=True, 246 | swallow_trailing_newline=True, 247 | ) 248 | 249 | # Make sure transform_newlines = False for [*], so [code] tags can be embedded 250 | # without transformation. 251 | def _render_list_item(name, value, options, parent, context): 252 | if not parent or parent.tag_name != "list": 253 | return "[*]%s
" % value 254 | 255 | return "
  • %s
  • " % value 256 | 257 | self.add_formatter( 258 | "*", 259 | _render_list_item, 260 | newline_closes=True, 261 | transform_newlines=False, 262 | same_tag_closes=True, 263 | strip=True, 264 | ) 265 | 266 | self.add_simple_formatter( 267 | "quote", 268 | "
    %(value)s
    ", 269 | strip=True, 270 | swallow_trailing_newline=True, 271 | ) 272 | self.add_simple_formatter( 273 | "code", 274 | "%(value)s", 275 | render_embedded=False, 276 | transform_newlines=False, 277 | swallow_trailing_newline=True, 278 | replace_cosmetic=False, 279 | ) 280 | self.add_simple_formatter( 281 | "center", '
    %(value)s
    ' 282 | ) 283 | 284 | def _render_color(name, value, options, parent, context): 285 | if "color" in options: 286 | color = options["color"].strip() 287 | elif options: 288 | color = list(options.keys())[0].strip() 289 | else: 290 | return value 291 | match = re.match(r"^([a-z]+)|^(#[a-f0-9]{3,6})", color, re.I) 292 | color = match.group() if match else "inherit" 293 | return '%(value)s' % { 294 | "color": color, 295 | "value": value, 296 | } 297 | 298 | self.add_formatter("color", _render_color) 299 | 300 | def _render_url(name, value, options, parent, context): 301 | if options and "url" in options: 302 | # Option values are not escaped for HTML output. 303 | href = self._replace(options["url"], self.REPLACE_ESCAPE) 304 | else: 305 | href = value 306 | # Completely ignore javascript: and data: "links". 307 | if re.sub(r"[^a-z0-9+]", "", href.lower().split(":", 1)[0]) in ( 308 | "javascript", 309 | "data", 310 | "vbscript", 311 | ): 312 | return "" 313 | # Only add the missing http:// if it looks like it starts with a domain name. 314 | if "://" not in href and _domain_re.match(href): 315 | href = "http://" + href 316 | return self.url_template.format(href=href.replace('"', "%22"), text=value) 317 | 318 | self.add_formatter( 319 | "url", _render_url, replace_links=False, replace_cosmetic=False 320 | ) 321 | 322 | def _replace(self, data, replacements): 323 | """ 324 | Given a list of 2-tuples (find, repl) this function performs all 325 | replacements on the input and returns the result. 326 | """ 327 | for find, repl in replacements: 328 | data = data.replace(find, repl) 329 | return data 330 | 331 | def _newline_tokenize(self, data): 332 | """ 333 | Given a string that does not contain any tags, this function will 334 | return a list of NEWLINE and DATA tokens such that if you concatenate 335 | their data, you will have the original string. 336 | """ 337 | parts = data.split("\n") 338 | tokens = [] 339 | for num, part in enumerate(parts): 340 | if part: 341 | tokens.append((self.TOKEN_DATA, None, None, part)) 342 | if num < (len(parts) - 1): 343 | tokens.append((self.TOKEN_NEWLINE, None, None, "\n")) 344 | return tokens 345 | 346 | def _parse_opts(self, data): 347 | """ 348 | Given a tag string, this function will parse any options out of it and 349 | return a tuple of (tag_name, options_dict). Options may be quoted in order 350 | to preserve spaces, and free-standing options are allowed. The tag name 351 | itself may also serve as an option if it is immediately followed by an equal 352 | sign. Here are some examples: 353 | quote author="Dan Watson" 354 | tag_name=quote, options={'author': 'Dan Watson'} 355 | url="http://test.com/s.php?a=bcd efg" popup 356 | tag_name=url, options={'url': 'http://test.com/s.php?a=bcd efg', 'popup': ''} 357 | """ 358 | name = None 359 | opts = CaseInsensitiveDict() 360 | in_value = False 361 | in_quote = False 362 | attr = "" 363 | value = "" 364 | attr_done = False 365 | stripped = data.strip() 366 | ls = len(stripped) 367 | pos = 0 368 | 369 | while pos < ls: 370 | ch = stripped[pos] 371 | if in_value: 372 | if in_quote: 373 | if ( 374 | ch == "\\" 375 | and ls > pos + 1 376 | and stripped[pos + 1] in ("\\", '"', "'") 377 | ): 378 | value += stripped[pos + 1] 379 | pos += 1 380 | elif ch == in_quote: 381 | in_quote = False 382 | in_value = False 383 | if attr: 384 | opts[attr] = value.strip() 385 | attr = "" 386 | value = "" 387 | else: 388 | value += ch 389 | else: 390 | if ch in ('"', "'"): 391 | in_quote = ch 392 | elif ch == " " and data.find("=", pos + 1) > 0: 393 | # If there is no = after this, the value may accept spaces. 394 | opts[attr] = value.strip() 395 | attr = "" 396 | value = "" 397 | in_value = False 398 | else: 399 | value += ch 400 | else: 401 | if ch == "=": 402 | in_value = True 403 | if name is None: 404 | name = attr 405 | elif ch == " ": 406 | attr_done = True 407 | else: 408 | if attr_done: 409 | if attr: 410 | if name is None: 411 | name = attr 412 | else: 413 | opts[attr] = "" 414 | attr = "" 415 | attr_done = False 416 | attr += ch 417 | pos += 1 418 | 419 | if attr: 420 | if name is None: 421 | name = attr 422 | opts[attr] = value.strip() 423 | return name.lower(), opts 424 | 425 | def _parse_tag(self, tag): 426 | """ 427 | Given a tag string (characters enclosed by []), this function will 428 | parse any options and return a tuple of the form: 429 | (valid, tag_name, closer, options) 430 | """ 431 | if ( 432 | not tag.startswith(self.tag_opener) 433 | or not tag.endswith(self.tag_closer) 434 | or ("\n" in tag) 435 | or ("\r" in tag) 436 | ): 437 | return (False, tag, False, None) 438 | tag_name = tag[len(self.tag_opener) : -len(self.tag_closer)].strip() 439 | if not tag_name: 440 | return (False, tag, False, None) 441 | closer = False 442 | opts = {} 443 | if tag_name[0] == "/": 444 | tag_name = tag_name[1:] 445 | closer = True 446 | # Parse options inside the opening tag, if needed. 447 | if (("=" in tag_name) or (" " in tag_name)) and not closer: 448 | tag_name, opts = self._parse_opts(tag_name) 449 | return (True, tag_name.strip().lower(), closer, opts) 450 | 451 | def _tag_extent(self, data, start): 452 | """ 453 | Finds the extent of a tag, accounting for option quoting and new tags starting 454 | before the current one closes. Returns (found_close, end_pos) where valid is 455 | False if another tag started before this one closed. 456 | """ 457 | in_quote = False 458 | quotable = False 459 | lto = len(self.tag_opener) 460 | ltc = len(self.tag_closer) 461 | for i in range(start + 1, len(data)): 462 | ch = data[i] 463 | if ch == "=": 464 | quotable = True 465 | if ch in ('"', "'"): 466 | if quotable and not in_quote: 467 | in_quote = ch 468 | elif in_quote == ch: 469 | in_quote = False 470 | quotable = False 471 | if not in_quote and data[i : i + lto] == self.tag_opener: 472 | return i, False 473 | if not in_quote and data[i : i + ltc] == self.tag_closer: 474 | return i + ltc, True 475 | return len(data), False 476 | 477 | def tokenize(self, data): 478 | """ 479 | Tokenizes the given string. A token is a 4-tuple of the form: 480 | 481 | (token_type, tag_name, tag_options, token_text) 482 | 483 | token_type 484 | One of: TOKEN_TAG_START, TOKEN_TAG_END, TOKEN_NEWLINE, TOKEN_DATA 485 | tag_name 486 | The name of the tag if token_type=TOKEN_TAG_*, otherwise None 487 | tag_options 488 | A dictionary of options specified for TOKEN_TAG_START, otherwise None 489 | token_text 490 | The original token text 491 | """ 492 | data = data.replace("\r\n", "\n").replace("\r", "\n") 493 | pos = start = end = 0 494 | ld = len(data) 495 | tokens = [] 496 | while pos < ld: 497 | start = data.find(self.tag_opener, pos) 498 | if start >= pos: 499 | # Check to see if there was data between this start and the last end. 500 | if start > pos: 501 | tl = self._newline_tokenize(data[pos:start]) 502 | tokens.extend(tl) 503 | pos = start 504 | 505 | # Find the extent of this tag, if it's ever closed. 506 | end, found_close = self._tag_extent(data, start) 507 | if found_close: 508 | tag = data[start:end] 509 | valid, tag_name, closer, opts = self._parse_tag(tag) 510 | # Make sure this is a well-formed, recognized tag, otherwise it's 511 | # just data. 512 | if valid and tag_name in self.recognized_tags: 513 | if closer: 514 | tokens.append((self.TOKEN_TAG_END, tag_name, None, tag)) 515 | else: 516 | tokens.append((self.TOKEN_TAG_START, tag_name, opts, tag)) 517 | elif ( 518 | valid 519 | and self.drop_unrecognized 520 | and tag_name not in self.recognized_tags 521 | ): 522 | # If we found a valid (but unrecognized) tag and self.drop_unrecognized is True, just drop it. 523 | pass 524 | else: 525 | tokens.extend(self._newline_tokenize(tag)) 526 | else: 527 | # We didn't find a closing tag, tack it on as text. 528 | tokens.extend(self._newline_tokenize(data[start:end])) 529 | pos = end 530 | else: 531 | # No more tags left to parse. 532 | break 533 | if pos < ld: 534 | tl = self._newline_tokenize(data[pos:]) 535 | tokens.extend(tl) 536 | return tokens 537 | 538 | def _find_closing_token(self, tag, tokens, pos): 539 | """ 540 | Given the current tag options, a list of tokens, and the current position 541 | in the token list, this function will find the position of the closing token 542 | associated with the specified tag. This may be a closing tag, a newline, or 543 | simply the end of the list (to ensure tags are closed). This function should 544 | return a tuple of the form (end_pos, consume), where consume should indicate 545 | whether the ending token should be consumed or not. 546 | """ 547 | embed_count = 0 548 | block_count = 0 549 | lt = len(tokens) 550 | while pos < lt: 551 | token_type, tag_name, tag_opts, token_text = tokens[pos] 552 | if token_type == self.TOKEN_DATA: 553 | # Short-circuit for performance. 554 | pos += 1 555 | continue 556 | if tag.newline_closes and token_type in ( 557 | self.TOKEN_TAG_START, 558 | self.TOKEN_TAG_END, 559 | ): 560 | # If we're finding the closing token for a tag that is closed by 561 | # newlines, but there is an embedded tag that doesn't transform newlines 562 | # (i.e. a code tag that keeps newlines intact), we need to skip over 563 | # that. 564 | inner_tag = self.recognized_tags[tag_name][1] 565 | if not inner_tag.transform_newlines: 566 | if token_type == self.TOKEN_TAG_START: 567 | block_count += 1 568 | else: 569 | block_count -= 1 570 | if ( 571 | token_type == self.TOKEN_NEWLINE 572 | and tag.newline_closes 573 | and block_count == 0 574 | ): 575 | # If for some crazy reason there are embedded tags that both close on 576 | # newline, the first newline will automatically close all those nested 577 | # tags. 578 | return pos, True 579 | elif token_type == self.TOKEN_TAG_START and tag_name == tag.tag_name: 580 | if tag.same_tag_closes: 581 | return pos, False 582 | if tag.render_embedded: 583 | embed_count += 1 584 | elif token_type == self.TOKEN_TAG_END and tag_name == tag.tag_name: 585 | if embed_count > 0: 586 | embed_count -= 1 587 | else: 588 | return pos, True 589 | pos += 1 590 | return pos, True 591 | 592 | def _link_replace(self, match, **context): 593 | """ 594 | Callback for re.sub to replace link text with markup. Turns out using a callback 595 | function is actually faster than using backrefs, plus this lets us provide a 596 | hook for user customization. linker_takes_context=True means that the linker 597 | gets passed context like a standard format function. 598 | """ 599 | url = match.group(0) 600 | if self.linker: 601 | if self.linker_takes_context: 602 | return self.linker(url, context) 603 | else: 604 | return self.linker(url) 605 | else: 606 | href = url 607 | if "://" not in href: 608 | href = "http://" + href 609 | # Escape quotes to avoid XSS, let the browser escape the rest. 610 | return self.url_template.format(href=href.replace('"', "%22"), text=url) 611 | 612 | def _transform( 613 | self, 614 | data, 615 | escape_html, 616 | replace_links, 617 | replace_cosmetic, 618 | transform_newlines, 619 | **context, 620 | ): 621 | """ 622 | Transforms the input string based on the options specified, taking into account 623 | whether the option is enabled globally for this parser. 624 | """ 625 | url_matches = {} 626 | if self.replace_links and replace_links: 627 | # If we're replacing links in the text (i.e. not those in [url] tags) then 628 | # we need to be careful to pull them out before doing any escaping or 629 | # cosmetic replacement. 630 | pos = 0 631 | while True: 632 | match = _url_re.search(data, pos) 633 | if not match: 634 | break 635 | # Replace any link with a token that we can substitute back in after replacements. 636 | token = "{{ bbcode-link-%s }}" % len(url_matches) 637 | url_matches[token] = self._link_replace(match, **context) 638 | start, end = match.span() 639 | data = data[:start] + token + data[end:] 640 | # To be perfectly accurate, this should probably be len(data[:start] + token), but 641 | # start will work, because the token itself won't match as a URL. 642 | pos = start 643 | if escape_html: 644 | data = self._replace(data, self.REPLACE_ESCAPE) 645 | if replace_cosmetic: 646 | data = self._replace(data, self.REPLACE_COSMETIC) 647 | # Now put the replaced links back in the text. 648 | for token, replacement in url_matches.items(): 649 | data = data.replace(token, replacement) 650 | if transform_newlines: 651 | data = data.replace("\n", "\r") 652 | return data 653 | 654 | def _format_tokens( 655 | self, 656 | tokens, 657 | parent, 658 | escape_html=None, 659 | replace_links=None, 660 | replace_cosmetic=None, 661 | transform_newlines=True, 662 | depth=1, 663 | **context, 664 | ): 665 | # Allow the parser defaults to be overridden when formatting. 666 | escape_html = self.escape_html if escape_html is None else escape_html 667 | replace_links = self.replace_links if replace_links is None else replace_links 668 | replace_cosmetic = ( 669 | self.replace_cosmetic if replace_cosmetic is None else replace_cosmetic 670 | ) 671 | idx = 0 672 | formatted = [] 673 | lt = len(tokens) 674 | while idx < lt: 675 | token_type, tag_name, tag_opts, token_text = tokens[idx] 676 | if token_type == self.TOKEN_TAG_START: 677 | render_func, tag = self.recognized_tags[tag_name] 678 | if tag.standalone: 679 | formatted.append( 680 | render_func(tag_name, None, tag_opts, parent, context) 681 | ) 682 | else: 683 | # First, find the extent of this tag's tokens. 684 | end, consume = self._find_closing_token(tag, tokens, idx + 1) 685 | subtokens = tokens[idx + 1 : end] 686 | # If the end tag should not be consumed, back up one (after grabbing the subtokens). 687 | if not consume: 688 | end = end - 1 689 | if tag.render_embedded and depth < self.max_tag_depth: 690 | # This tag renders embedded tags, simply recurse. 691 | inner = self._format_tokens( 692 | subtokens, tag, depth=depth + 1, **context 693 | ) 694 | else: 695 | # Otherwise, just concatenate all the token text. 696 | inner = self._transform( 697 | "".join([t[3] for t in subtokens]), 698 | tag.escape_html, 699 | tag.replace_links, 700 | tag.replace_cosmetic, 701 | tag.transform_newlines, 702 | **context, 703 | ) 704 | if tag.strip: 705 | inner = inner.strip() 706 | # Append the rendered contents. 707 | formatted.append( 708 | render_func(tag_name, inner, tag_opts, parent, context) 709 | ) 710 | # If the tag should swallow the first trailing newline, check the token after the closing token. 711 | if tag.swallow_trailing_newline: 712 | next_pos = end + 1 713 | if ( 714 | next_pos < len(tokens) 715 | and tokens[next_pos][0] == self.TOKEN_NEWLINE 716 | ): 717 | end = next_pos 718 | # Skip to the end tag. 719 | idx = end 720 | elif token_type == self.TOKEN_NEWLINE: 721 | # If this is a top-level newline, replace it. Otherwise, it will be replaced (if necessary) 722 | # by the code above. 723 | formatted.append( 724 | "\r" if parent is None or parent.transform_newlines else token_text 725 | ) 726 | elif token_type == self.TOKEN_DATA: 727 | escape = escape_html if parent is None else parent.escape_html 728 | links = replace_links if parent is None else parent.replace_links 729 | cosmetic = ( 730 | replace_cosmetic if parent is None else parent.replace_cosmetic 731 | ) 732 | newlines = ( 733 | transform_newlines if parent is None else parent.transform_newlines 734 | ) 735 | formatted.append( 736 | self._transform( 737 | token_text, escape, links, cosmetic, newlines, **context 738 | ) 739 | ) 740 | idx += 1 741 | return "".join(formatted) 742 | 743 | def format(self, data, **context): 744 | """ 745 | Formats the input text using any installed renderers. Any context keyword 746 | arguments given here will be passed along to the render functions as a context 747 | dictionary. 748 | """ 749 | tokens = self.tokenize(data) 750 | full_context = self.default_context.copy() 751 | full_context.update(context) 752 | return self._format_tokens(tokens, None, **full_context).replace( 753 | "\r", self.newline 754 | ) 755 | 756 | def strip(self, data, strip_newlines=False): 757 | """ 758 | Strips out any tags from the input text, using the same tokenization as the 759 | formatter. 760 | """ 761 | text = [] 762 | for token_type, tag_name, tag_opts, token_text in self.tokenize(data): 763 | if token_type == self.TOKEN_DATA: 764 | text.append(token_text) 765 | elif token_type == self.TOKEN_NEWLINE and not strip_newlines: 766 | text.append(token_text) 767 | return "".join(text) 768 | 769 | 770 | g_parser = None 771 | 772 | 773 | def render_html(input_text, **context): 774 | """ 775 | A module-level convenience method that creates a default bbcode parser, 776 | and renders the input string as HTML. 777 | """ 778 | global g_parser 779 | if g_parser is None: 780 | g_parser = Parser() 781 | return g_parser.format(input_text, **context) 782 | 783 | 784 | def main(): 785 | sys.stdout.write(render_html(sys.stdin.read())) 786 | sys.stdout.write("\n") 787 | sys.stdout.flush() 788 | 789 | 790 | if __name__ == "__main__": 791 | main() 792 | -------------------------------------------------------------------------------- /docs/formatters.md: -------------------------------------------------------------------------------- 1 | ## Advanced Tag Formatters 2 | 3 | [Simple formatters](index.md) are great for basic string substitution tags. But if you need to handle tag options, or 4 | have access to the parser context or parent tag, you can write a formatter function that returns whatever HTML you like: 5 | 6 | ```python 7 | # A custom render function that uses the tag name as a color style. 8 | def render_color(tag_name, value, options, parent, context): 9 | return '%s' % (tag_name, value) 10 | 11 | # Installing advanced formatters. 12 | for color in ('red', 'blue', 'green', 'yellow', 'black', 'white'): 13 | parser.add_formatter(color, render_color) 14 | ``` 15 | 16 | 17 | ## Advanced Quote Example 18 | 19 | Suppose you want to support an author option on your quote tags. Your formatting function might look something like 20 | this: 21 | 22 | ```python 23 | def render_quote(tag_name, value, options, parent, context): 24 | author = u'' 25 | # [quote author=Somebody] 26 | if 'author' in options: 27 | author = options['author'] 28 | # [quote=Somebody] 29 | elif 'quote' in options: 30 | author = options['quote'] 31 | # [quote Somebody] 32 | elif len(options) == 1: 33 | key, val = list(options.items())[0] 34 | if val: 35 | author = val 36 | elif key: 37 | author = key 38 | # [quote Firstname Lastname] 39 | elif options: 40 | author = ' '.join([k for k in options.keys()]) 41 | extra = '%s' % author if author else '' 42 | return '

    %s

    %s
    ' % (value, extra) 43 | 44 | # Now register our new quote tag, telling it to strip off whitespace, and the newline after the [/quote]. 45 | parser.add_formatter('quote', render_quote, strip=True, swallow_trailing_newline=True) 46 | ``` 47 | 48 | ## Custom Tag Options 49 | 50 | When registering a formatter (simple or advanced), you may pass several keyword options for controlling the parsing and 51 | rendering behavior. 52 | 53 | * `newline_closes=False` - True if a newline should automatically close this tag. 54 | * `same_tag_closes=False` - True if another start of the same tag should automatically close this tag. 55 | * `standalone=False` - True if this tag does not have a closing tag. 56 | * `render_embedded=True` - True if tags should be rendered inside this tag. 57 | * `transform_newlines=True` - True if newlines should be converted to markup. 58 | * `escape_html=True` - True if HTML characters (<, >, and &) should be escaped inside this tag. 59 | * `replace_links=True` - True if URLs should be replaced with link markup inside this tag. 60 | * `replace_cosmetic=True` - True if cosmetic replacements (elipses, dashes, etc.) should be performed inside this tag. 61 | * `strip=False` - True if leading and trailing whitespace should be stripped inside this tag. 62 | * `swallow_trailing_newline=False` - True if this tag should swallow the first trailing newline (i.e. for block 63 | elements). 64 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Basic Usage 2 | 3 | If you need only the [built-in tags](tags.md), you can simply use the global default parser: 4 | 5 | ```python 6 | import bbcode 7 | html = bbcode.render_html(text) 8 | ``` 9 | 10 | Basic formatters can be added using simple string substitution. For instance, adding a [wiki] tag for wikipedia links 11 | may look like: 12 | 13 | ```python 14 | parser = bbcode.Parser() 15 | parser.add_simple_formatter('wiki', '%(value)s') 16 | ``` 17 | 18 | 19 | ## Custom Parser Objects 20 | 21 | The bbcode ``Parser`` class takes several options when creating: 22 | 23 | * `newline="
    "` - What to replace newlines with. 24 | * `install_defaults=True` - Whether to install the default tag formatters. If `False`, you will need to specify add tag 25 | formatters yourself. 26 | * `escape_html=True` - Whether to escape special HTML characters (<, >, &, ", and '). Replacements are specified as 27 | tuples in `Parser.REPLACE_ESCAPE`. 28 | * `replace_links=True` - Whether to automatically create HTML links for URLs in the source text. 29 | * `replace_cosmetic=True` - Whether to perform cosmetic replacements for ---, --, ..., (c), (reg), and (tm). 30 | Replacements are specified as tuples in `Parser.REPLACE_COSMETIC`. 31 | * `tag_opener="["` - The opening tag character(s). 32 | * `tag_closer="]"` - The closing tag character(s). 33 | * `linker=None` - A function that takes a regular expression match object (and optionally the `Parser` context) and 34 | returns an HTML replacement string. 35 | * `linker_takes_context=False` - Whether the linker function accepts a second `context` parameter. If `True`, the linker 36 | function will be passed the context sent to `Parser.format`. 37 | * `drop_unrecognized=False` - Whether to drop unrecognized (but valid) tags. The default is to leave the tags 38 | (unformatted) in the output. 39 | * `default_context={}` - A dictionary to use as the default context when rendering. Keywords arguments passed to 40 | `format` will supercede these defaults. 41 | * `url_template="{text}"` - The URL template allows you to customize how urls are 42 | transformaed into HTML. For instance, to add "target='_blank'", you may use something like: 43 | `"{text}"` 44 | 45 | 46 | ## Customizing the Linker 47 | 48 | The linker is a function that gets called to replace URLs with markup. It takes one or two arguments (depending on 49 | whether you set ``linker_takes_context``), and might look like this: 50 | 51 | ```python 52 | def my_linker(url): 53 | href = url 54 | if '://' not in href: 55 | href = 'http://' + href 56 | return '%s' % (href, url) 57 | 58 | parser = bbcode.Parser(linker=my_linker) 59 | parser.format('www.apple.com') # returns www.apple.com 60 | ``` 61 | 62 | For an example of a linker that may want the render context, imagine a linker that routes all clicks through a local 63 | URL: 64 | 65 | ```python 66 | def my_linker(url, context): 67 | href = url 68 | if '://' not in href: 69 | href = 'http://' + href 70 | redir_url = context['request'].build_absolute_url('/redirect/') + '?to=' + urllib.quote(href, safe='/') 71 | return '%s' % (redir_url, url) 72 | 73 | parser = bbcode.Parser(linker=my_linker, linker_takes_context=True) 74 | parser.format('www.apple.com', request=request) 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/tags.md: -------------------------------------------------------------------------------- 1 | Built-In Tags 2 | ============= 3 | 4 | Below are the tag formatters that are built into ``bbcode`` by default: 5 | 6 | Tag | Input | Output 7 | -------|--------------------------------|---------------------------------------------- 8 | b | [b]test[/b] | `test` 9 | i | [i]test[/i] | `test` 10 | u | [u]test[/u] | `test` 11 | s | [s]test[/s] | `test` 12 | hr | [hr] | `
    ` 13 | sub | x[sub]3[/sub] | `x3` 14 | sup | x[sup]3[/sup] | `x3` 15 | list/* | [list][*] item[/list] | `` 16 | quote | [quote]hello[/quote] | `
    hello
    ` 17 | code | [code]x = 3[/code] | `x = 3` 18 | center | [center]hello[/center] | `
    hello
    ` 19 | color | [color=red]red[/color] | `red` 20 | url | [url=www.apple.com]Apple[/url] | `Apple` 21 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: bbcode 2 | nav: 3 | - Home: index.md 4 | - Tags: tags.md 5 | - Formatters: formatters.md 6 | theme: 7 | name: material 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "bbcode" 3 | dynamic = ["version"] 4 | description = "A pure python bbcode parser and formatter." 5 | authors = [ 6 | { name = "Dan Watson", email = "dcwatson@gmail.com" } 7 | ] 8 | readme = "README.md" 9 | requires-python = ">= 3.9" 10 | license = { text = "BSD" } 11 | classifiers = [ 12 | "Development Status :: 5 - Production/Stable", 13 | "Intended Audience :: Developers", 14 | "License :: OSI Approved :: BSD License", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Topic :: Text Processing :: Markup", 18 | ] 19 | 20 | [project.urls] 21 | Homepage = "https://github.com/dcwatson/bbcode" 22 | Documentation = "https://dcwatson.github.io/bbcode/" 23 | 24 | [project.scripts] 25 | bbcode = "bbcode:main" 26 | 27 | [build-system] 28 | requires = ["hatchling"] 29 | build-backend = "hatchling.build" 30 | 31 | [tool.hatch.version] 32 | path = "bbcode.py" 33 | 34 | [tool.ruff.lint] 35 | extend-select = ["I"] 36 | 37 | [tool.uv] 38 | dev-dependencies = [ 39 | "mkdocs-material>=9.5.42", 40 | "mkdocs>=1.6.1", 41 | ] 42 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import bbcode 4 | 5 | 6 | class ParserTests(unittest.TestCase): 7 | TESTS = ( 8 | ("[B]hello world[/b]", "hello world"), 9 | ("[b][i]test[/i][/b]", "test"), 10 | ("[b][i]test[/b][/i]", "test"), 11 | ("[b]hello [i]world[/i]", "hello world"), 12 | ("[tag][soup][/tag]", "[tag][soup][/tag]"), 13 | ("[b]hello [ world[/b]", "hello [ world"), 14 | ("[b]]he[llo [ w]orld[/b]", "]he[llo [ w]orld"), 15 | ("[b]hello [] world[/b]", "hello [] world"), 16 | ("[/asdf][/b]", "[/asdf]"), 17 | ("[list]\n[*]one\n[*]two\n[/list]", ""), 18 | ( 19 | "[list=1]\n[*]one\n[*]two\n[/list]", 20 | '
    1. one
    2. two
    ', 21 | ), 22 | ("[*]hello\n[*]world\n", "[*]hello
    [*]world
    "), 23 | ( 24 | "[b][*]hello\n[*]world\n[/b]", 25 | "[*]hello
    [*]world
    ", 26 | ), 27 | ("[b\n oops [i]i[/i] forgot[/b]", "[b
    oops i forgot"), 28 | ("[b]over[i]lap[/b]ped[/i]", "overlapped"), 29 | (">> hey -- a dash...", ">> hey – a dash…"), 30 | ( 31 | "[url]http://foo.com/s.php?some--data[/url]", 32 | 'http://foo.com/s.php?some--data', 33 | ), 34 | ( 35 | "[url=apple.com]link[/url]", 36 | 'link', 37 | ), 38 | ( 39 | "www.apple.com blah foo.com/bar", 40 | 'www.apple.com blah ' 41 | 'foo.com/bar', 42 | ), 43 | ( 44 | "[color=red]hey now [url=apple.com]link[/url][/color]", 45 | 'hey now link', 46 | ), 47 | ( 48 | "[ b ] hello [u] world [/u] [ /b ]", 49 | " hello world ", 50 | ), 51 | ( 52 | "[quote] \r\ntesting\nstrip [/quote]", 53 | "
    testing
    strip
    ", 54 | ), 55 | ( 56 | "[color red]this is red[/color]", 57 | 'this is red', 58 | ), 59 | ("[color]nothing[/color]", "nothing"), 60 | ( 61 | '[url=""]xss[/url]', 62 | 'xss', 63 | ), 64 | ( 65 | "[color=]xss[/color]", 66 | 'xss', 67 | ), 68 | # Known issue: since HTML is escaped first, the trailing > is captured by the URL regex. 69 | # ('', '<http://foo.com/blah_blah>'), 70 | ("[COLOR=red]hello[/color]", 'hello'), 71 | ( 72 | "[URL=apple.com]link[/URL]", 73 | 'link', 74 | ), 75 | ( 76 | "[list] [*]Entry 1 [*]Entry 2 [*]Entry 3 [/list]", 77 | "", 78 | ), 79 | ( 80 | "[url=relative/url.html]link[/url]", 81 | 'link', 82 | ), 83 | ( 84 | "[url=/absolute/url.html]link[/url]", 85 | 'link', 86 | ), 87 | ("[url=test.html]page[/url]", 'page'), 88 | ("[URL=ñó]page[/URL]", 'page'), 89 | # Tests to make sure links don't get cosmetic replacements. 90 | ( 91 | "[url=http://test.com/my--page]test[/url]", 92 | 'test', 93 | ), 94 | ( 95 | "http://test.com/my...page(c)", 96 | 'http://test.com/my...page(c)', 97 | ), 98 | ( 99 | "multiple http://apple.com/page link http://foo.com/foo--bar test", 100 | 'multiple http://apple.com/page ' 101 | 'link http://foo.com/foo--bar test', 102 | ), 103 | ( 104 | '[url=http://foo.com][/url]', 105 | '<script>alert("XSS");</script>', 106 | ), 107 | ( 108 | "[url]123\" onmouseover=\"alert('Hacked');[/url]", 109 | '' 110 | "123" onmouseover="alert('Hacked');", 111 | ), 112 | ( 113 | "[code python]lambda code: [code] + [1, 2][/code]", 114 | "lambda code: [code] + [1, 2]", 115 | ), 116 | ( 117 | '[color="red; font-size:1000px;"]test[/color]', 118 | 'test', 119 | ), 120 | ("[color=#f4f4C3 barf]hi[/color]", 'hi'), 121 | ( 122 | "[list]\n[*]item with[code]some\ncode[/code] and text after[/list]", 123 | "", 124 | ), 125 | ("x[sub]test[/sub]y", "xtesty"), 126 | ("x[sup]3[/sup] + 7", "x3 + 7"), 127 | ("line one[hr]line two", "line one
    line two"), 128 | ("hello :[ world", "hello :[ world"), 129 | ('[url]javascript:alert("XSS");[/url]', ""), 130 | ("[url]\x01javascript:alert(1)[/url]", ""), 131 | ("[url]javascript\x01:alert(1)[/url]", ""), 132 | ("[url]vbscript:alert(1)[/url]", ""), 133 | ( 134 | 'http://www.google.com"onmousemove="alert(\'XSS\');"', 135 | '' 136 | "http://www.google.com\"onmousemove=\"alert('XSS');"", 137 | ), 138 | ( 139 | "[url=data:text/html;base64,PHNjcmlwdD5hbGVydCgiMSIpOzwvc2NyaXB0Pg==]xss[/url]", 140 | "", 141 | ), 142 | ("[color='red']single[/color]", 'single'), 143 | ('[quote author="name][clan"]blah[/quote]', "
    blah
    "), 144 | ( 145 | "http://github.com/ http://example.org http://github.com/dcwatson/", 146 | 'http://github.com/ ' 147 | 'http://example.org ' 148 | 'http://github.com/dcwatson/', 149 | ), 150 | ('[b]Hello, [wor"ld][/b] out', "Hello, [wor"ld] out"), 151 | ( 152 | "[center]a\nb[code]c\nd[/code]\ne\nf\n", 153 | '
    a
    bc\nde
    f
    ', 154 | ), 155 | ( 156 | '[b]test[/b]', 157 | "<a id="test">test</a>", 158 | ), 159 | ("[code]--[/code]", "--"), 160 | ) 161 | 162 | URL_TESTS = """ 163 | http://foo.com/blah_blah 164 | (Something like http://foo.com/blah_blah) 165 | http://foo.com/blah_blah_(wikipedia) 166 | http://foo.com/more_(than)_one_(parens) 167 | (Something like http://foo.com/blah_blah_(wikipedia)) 168 | http://foo.com/blah_(wikipedia)#cite-1 169 | http://foo.com/blah_(wikipedia)_blah#cite-1 170 | http://foo.com/(something)?after=parens 171 | http://foo.com/blah_blah. 172 | http://foo.com/blah_blah/. 173 | 174 | 175 | http://foo.com/blah_blah, 176 | http://www.extinguishedscholar.com/wpglob/?p=364. 177 | http://example.com 178 | Just a www.example.com link. 179 | http://example.com/something?with,commas,in,url, but not at end 180 | bit.ly/foo 181 | http://asdf.xxxx.yyyy.com/vvvvv/PublicPages/Login.aspx?ReturnUrl=%2fvvvvv%2f(asdf@qwertybean.com/qwertybean) 182 | """.strip() 183 | 184 | def setUp(self): 185 | self.parser = bbcode.Parser() 186 | 187 | def test_format(self): 188 | for src, expected in self.TESTS: 189 | result = self.parser.format(src) 190 | self.assertEqual(result, expected) 191 | 192 | def test_max_depth(self): 193 | limit_one_parser = bbcode.Parser(max_tag_depth=1) 194 | limit_two_parser = bbcode.Parser(max_tag_depth=2) 195 | unlimited_parser = bbcode.Parser() 196 | 197 | src = "[quote][quote][quote]foo[/quote][/quote][/quote]" 198 | limit_one_expected = ( 199 | "
    [quote][quote]foo[/quote][/quote]
    " 200 | ) 201 | limit_two_expected = ( 202 | "
    [quote]foo[/quote]
    " 203 | ) 204 | unlimited_expected = "
    foo
    " 205 | 206 | self.assertEqual(limit_one_parser.format(src), limit_one_expected) 207 | self.assertEqual(limit_two_parser.format(src), limit_two_expected) 208 | self.assertEqual(unlimited_parser.format(src), unlimited_expected) 209 | 210 | def test_parse_opts(self): 211 | tag_name, opts = self.parser._parse_opts( 212 | 'url="http://test.com/s.php?a=bcd efg" popup' 213 | ) 214 | self.assertEqual(tag_name, "url") 215 | self.assertEqual(opts, {"url": "http://test.com/s.php?a=bcd efg", "popup": ""}) 216 | tag_name, opts = self.parser._parse_opts('tag sep="=" flag=1') 217 | self.assertEqual(tag_name, "tag") 218 | self.assertEqual(opts, {"sep": "=", "flag": "1"}) 219 | tag_name, opts = self.parser._parse_opts( 220 | " quote opt1 opt2 author = Watson, Dan " 221 | ) 222 | self.assertEqual(tag_name, "quote") 223 | self.assertEqual(opts, {"author": "Watson, Dan", "opt1": "", "opt2": ""}) 224 | tag_name, opts = self.parser._parse_opts("quote = Watson, Dan") 225 | self.assertEqual(tag_name, "quote") 226 | self.assertEqual(opts, {"quote": "Watson, Dan"}) 227 | tag_name, opts = self.parser._parse_opts("""Quote='Dan "Darsh" Watson'""") 228 | self.assertEqual(tag_name, "quote") 229 | self.assertEqual(opts, {"quote": 'Dan "Darsh" Watson'}) 230 | # combine single and double quotes in a value 231 | tag_name, opts = self.parser._parse_opts( 232 | "quote='Lan \"Please Don\\'t\" Rogers'" 233 | ) 234 | self.assertEqual(tag_name, "quote") 235 | self.assertEqual(opts, {"quote": 'Lan "Please Don\'t" Rogers'}) 236 | # Ensure backslash is still representable 237 | tag_name, opts = self.parser._parse_opts("""quote='\\\\"q\\\\"'""") 238 | self.assertEqual(tag_name, "quote") 239 | self.assertEqual(opts, {"quote": '\\"q\\"'}) 240 | # Make sure lookahead for unescaping doesn't go OOB 241 | tag_name, opts = self.parser._parse_opts("quote='\\") 242 | self.assertEqual(tag_name, "quote") 243 | self.assertEqual(opts, {"quote": "\\"}) 244 | tag_name, opts = self.parser._parse_opts("quote=Back\\Slash") 245 | self.assertEqual(tag_name, "quote") 246 | self.assertEqual(opts, {"quote": "Back\\Slash"}) 247 | tag_name, opts = self.parser._parse_opts("quote=something author=other") 248 | self.assertEqual(tag_name, "quote") 249 | self.assertEqual(opts, {"quote": "something", "author": "other"}) 250 | 251 | def test_strip(self): 252 | result = self.parser.strip( 253 | "[b]hello \n[i]world[/i][/b] -- []", strip_newlines=True 254 | ) 255 | self.assertEqual(result, "hello world -- []") 256 | html_parser = bbcode.Parser( 257 | tag_opener="<", tag_closer=">", drop_unrecognized=True 258 | ) 259 | result = html_parser.strip( 260 | '
    hello world
    ' 261 | ) 262 | self.assertEqual(result, "hello world") 263 | 264 | def test_linker(self): 265 | def _contextual_link(url, context): 266 | return '%s' % ( 267 | url, 268 | context["substitution"], 269 | ) 270 | 271 | def _link(url): 272 | return _contextual_link(url, {"substitution": url}) 273 | 274 | # Test noncontextual linker 275 | p = bbcode.Parser(linker=_link) 276 | s = p.format("hello www.apple.com world") 277 | self.assertEqual( 278 | s, 'hello www.apple.com world' 279 | ) 280 | # Test contextual linker 281 | p = bbcode.Parser(linker=_contextual_link, linker_takes_context=True) 282 | s = p.format("hello www.apple.com world", substitution="oh hai") 283 | self.assertEqual( 284 | s, 'hello oh hai world' 285 | ) 286 | # Test default context in linker 287 | p = bbcode.Parser( 288 | linker=_contextual_link, 289 | linker_takes_context=True, 290 | default_context={"substitution": "arf"}, 291 | ) 292 | s = p.format("hello www.apple.com world") 293 | self.assertEqual( 294 | s, 'hello arf world' 295 | ) 296 | 297 | def test_urls(self): 298 | for line in self.URL_TESTS.splitlines(): 299 | num = len(bbcode._url_re.findall(line)) 300 | self.assertEqual(num, 1, 'Found %d links in "%s"' % (num, line.strip())) 301 | 302 | def test_unicode(self): 303 | src = "[center]ƒünk¥ • §tüƒƒ[/center]" 304 | dst = '
    ƒünk¥ • §tüƒƒ
    ' 305 | self.assertEqual(self.parser.format(src), dst) 306 | 307 | def test_format_overrides(self): 308 | formatted = self.parser.format( 309 | '[b]test[/b]', escape_html=False 310 | ) 311 | self.assertEqual(formatted, 'test') 312 | formatted = self.parser.format( 313 | 'Apple(c)', 314 | escape_html=False, 315 | replace_links=False, 316 | ) 317 | self.assertEqual(formatted, 'Apple©') 318 | 319 | def test_default_context(self): 320 | def _render_context(tag_name, value, options, parent, context): 321 | return context["hello"] 322 | 323 | parser = bbcode.Parser(default_context={"hello": "world"}) 324 | parser.add_formatter("c", _render_context) 325 | self.assertEqual(parser.format("[c]test[/c]"), "world") 326 | 327 | def test_options_case(self): 328 | def _render_author(tag_name, value, options, parent, context): 329 | self.assertIn("dan", options) 330 | self.assertEqual(options["wAtSoN"], "1") 331 | return " ".join(key for key, value in options.items()) 332 | 333 | parser = bbcode.Parser() 334 | parser.add_formatter("author", _render_author) 335 | self.assertEqual( 336 | parser.format("[author Dan Watson=1]whatever[/author]"), "Dan Watson" 337 | ) 338 | 339 | def test_render_html(self): 340 | html = bbcode.render_html("[b]hello[/b] [i]world[/i]") 341 | self.assertEqual(html, "hello world") 342 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.9" 3 | 4 | [[package]] 5 | name = "babel" 6 | version = "2.16.0" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, 11 | ] 12 | 13 | [[package]] 14 | name = "bbcode" 15 | version = "1.2.0" 16 | source = { editable = "." } 17 | 18 | [package.dev-dependencies] 19 | dev = [ 20 | { name = "mkdocs" }, 21 | { name = "mkdocs-material" }, 22 | ] 23 | 24 | [package.metadata] 25 | 26 | [package.metadata.requires-dev] 27 | dev = [ 28 | { name = "mkdocs", specifier = ">=1.6.1" }, 29 | { name = "mkdocs-material", specifier = ">=9.5.42" }, 30 | ] 31 | 32 | [[package]] 33 | name = "certifi" 34 | version = "2024.8.30" 35 | source = { registry = "https://pypi.org/simple" } 36 | sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } 37 | wheels = [ 38 | { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, 39 | ] 40 | 41 | [[package]] 42 | name = "charset-normalizer" 43 | version = "3.4.0" 44 | source = { registry = "https://pypi.org/simple" } 45 | sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } 46 | wheels = [ 47 | { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, 48 | { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, 49 | { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, 50 | { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, 51 | { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, 52 | { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, 53 | { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, 54 | { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, 55 | { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, 56 | { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, 57 | { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, 58 | { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, 59 | { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, 60 | { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, 61 | { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, 62 | { url = "https://files.pythonhosted.org/packages/9c/61/73589dcc7a719582bf56aae309b6103d2762b526bffe189d635a7fcfd998/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", size = 193339 }, 63 | { url = "https://files.pythonhosted.org/packages/77/d5/8c982d58144de49f59571f940e329ad6e8615e1e82ef84584c5eeb5e1d72/charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", size = 124366 }, 64 | { url = "https://files.pythonhosted.org/packages/bf/19/411a64f01ee971bed3231111b69eb56f9331a769072de479eae7de52296d/charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", size = 118874 }, 65 | { url = "https://files.pythonhosted.org/packages/4c/92/97509850f0d00e9f14a46bc751daabd0ad7765cff29cdfb66c68b6dad57f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", size = 138243 }, 66 | { url = "https://files.pythonhosted.org/packages/e2/29/d227805bff72ed6d6cb1ce08eec707f7cfbd9868044893617eb331f16295/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", size = 148676 }, 67 | { url = "https://files.pythonhosted.org/packages/13/bc/87c2c9f2c144bedfa62f894c3007cd4530ba4b5351acb10dc786428a50f0/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", size = 141289 }, 68 | { url = "https://files.pythonhosted.org/packages/eb/5b/6f10bad0f6461fa272bfbbdf5d0023b5fb9bc6217c92bf068fa5a99820f5/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", size = 142585 }, 69 | { url = "https://files.pythonhosted.org/packages/3b/a0/a68980ab8a1f45a36d9745d35049c1af57d27255eff8c907e3add84cf68f/charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", size = 144408 }, 70 | { url = "https://files.pythonhosted.org/packages/d7/a1/493919799446464ed0299c8eef3c3fad0daf1c3cd48bff9263c731b0d9e2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", size = 139076 }, 71 | { url = "https://files.pythonhosted.org/packages/fb/9d/9c13753a5a6e0db4a0a6edb1cef7aee39859177b64e1a1e748a6e3ba62c2/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", size = 146874 }, 72 | { url = "https://files.pythonhosted.org/packages/75/d2/0ab54463d3410709c09266dfb416d032a08f97fd7d60e94b8c6ef54ae14b/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", size = 150871 }, 73 | { url = "https://files.pythonhosted.org/packages/8d/c9/27e41d481557be53d51e60750b85aa40eaf52b841946b3cdeff363105737/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", size = 148546 }, 74 | { url = "https://files.pythonhosted.org/packages/ee/44/4f62042ca8cdc0cabf87c0fc00ae27cd8b53ab68be3605ba6d071f742ad3/charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", size = 143048 }, 75 | { url = "https://files.pythonhosted.org/packages/01/f8/38842422988b795220eb8038745d27a675ce066e2ada79516c118f291f07/charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", size = 94389 }, 76 | { url = "https://files.pythonhosted.org/packages/0b/6e/b13bd47fa9023b3699e94abf565b5a2f0b0be6e9ddac9812182596ee62e4/charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", size = 101752 }, 77 | { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, 78 | { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, 79 | { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, 80 | { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, 81 | { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, 82 | { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, 83 | { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, 84 | { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, 85 | { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, 86 | { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, 87 | { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, 88 | { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, 89 | { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, 90 | { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, 91 | { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, 92 | { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, 93 | { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, 94 | { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, 95 | { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, 96 | { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, 97 | { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, 98 | { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, 99 | { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, 100 | { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, 101 | { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, 102 | { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, 103 | { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, 104 | { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, 105 | { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, 106 | { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, 107 | { url = "https://files.pythonhosted.org/packages/54/2f/28659eee7f5d003e0f5a3b572765bf76d6e0fe6601ab1f1b1dd4cba7e4f1/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", size = 196326 }, 108 | { url = "https://files.pythonhosted.org/packages/d1/18/92869d5c0057baa973a3ee2af71573be7b084b3c3d428fe6463ce71167f8/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", size = 125614 }, 109 | { url = "https://files.pythonhosted.org/packages/d6/27/327904c5a54a7796bb9f36810ec4173d2df5d88b401d2b95ef53111d214e/charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", size = 120450 }, 110 | { url = "https://files.pythonhosted.org/packages/a4/23/65af317914a0308495133b2d654cf67b11bbd6ca16637c4e8a38f80a5a69/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", size = 140135 }, 111 | { url = "https://files.pythonhosted.org/packages/f2/41/6190102ad521a8aa888519bb014a74251ac4586cde9b38e790901684f9ab/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", size = 150413 }, 112 | { url = "https://files.pythonhosted.org/packages/7b/ab/f47b0159a69eab9bd915591106859f49670c75f9a19082505ff16f50efc0/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", size = 142992 }, 113 | { url = "https://files.pythonhosted.org/packages/28/89/60f51ad71f63aaaa7e51a2a2ad37919985a341a1d267070f212cdf6c2d22/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", size = 144871 }, 114 | { url = "https://files.pythonhosted.org/packages/0c/48/0050550275fea585a6e24460b42465020b53375017d8596c96be57bfabca/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", size = 146756 }, 115 | { url = "https://files.pythonhosted.org/packages/dc/b5/47f8ee91455946f745e6c9ddbb0f8f50314d2416dd922b213e7d5551ad09/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", size = 141034 }, 116 | { url = "https://files.pythonhosted.org/packages/84/79/5c731059ebab43e80bf61fa51666b9b18167974b82004f18c76378ed31a3/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", size = 149434 }, 117 | { url = "https://files.pythonhosted.org/packages/ca/f3/0719cd09fc4dc42066f239cb3c48ced17fc3316afca3e2a30a4756fe49ab/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", size = 152443 }, 118 | { url = "https://files.pythonhosted.org/packages/f7/0e/c6357297f1157c8e8227ff337e93fd0a90e498e3d6ab96b2782204ecae48/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", size = 150294 }, 119 | { url = "https://files.pythonhosted.org/packages/54/9a/acfa96dc4ea8c928040b15822b59d0863d6e1757fba8bd7de3dc4f761c13/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", size = 145314 }, 120 | { url = "https://files.pythonhosted.org/packages/73/1c/b10a63032eaebb8d7bcb8544f12f063f41f5f463778ac61da15d9985e8b6/charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", size = 94724 }, 121 | { url = "https://files.pythonhosted.org/packages/c5/77/3a78bf28bfaa0863f9cfef278dbeadf55efe064eafff8c7c424ae3c4c1bf/charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", size = 102159 }, 122 | { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, 123 | ] 124 | 125 | [[package]] 126 | name = "click" 127 | version = "8.1.7" 128 | source = { registry = "https://pypi.org/simple" } 129 | dependencies = [ 130 | { name = "colorama", marker = "platform_system == 'Windows'" }, 131 | ] 132 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } 133 | wheels = [ 134 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, 135 | ] 136 | 137 | [[package]] 138 | name = "colorama" 139 | version = "0.4.6" 140 | source = { registry = "https://pypi.org/simple" } 141 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 142 | wheels = [ 143 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 144 | ] 145 | 146 | [[package]] 147 | name = "ghp-import" 148 | version = "2.1.0" 149 | source = { registry = "https://pypi.org/simple" } 150 | dependencies = [ 151 | { name = "python-dateutil" }, 152 | ] 153 | sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } 154 | wheels = [ 155 | { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, 156 | ] 157 | 158 | [[package]] 159 | name = "idna" 160 | version = "3.10" 161 | source = { registry = "https://pypi.org/simple" } 162 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 163 | wheels = [ 164 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 165 | ] 166 | 167 | [[package]] 168 | name = "importlib-metadata" 169 | version = "8.5.0" 170 | source = { registry = "https://pypi.org/simple" } 171 | dependencies = [ 172 | { name = "zipp" }, 173 | ] 174 | sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } 175 | wheels = [ 176 | { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, 177 | ] 178 | 179 | [[package]] 180 | name = "jinja2" 181 | version = "3.1.4" 182 | source = { registry = "https://pypi.org/simple" } 183 | dependencies = [ 184 | { name = "markupsafe" }, 185 | ] 186 | sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 } 187 | wheels = [ 188 | { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 }, 189 | ] 190 | 191 | [[package]] 192 | name = "markdown" 193 | version = "3.7" 194 | source = { registry = "https://pypi.org/simple" } 195 | dependencies = [ 196 | { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, 197 | ] 198 | sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } 199 | wheels = [ 200 | { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, 201 | ] 202 | 203 | [[package]] 204 | name = "markupsafe" 205 | version = "3.0.2" 206 | source = { registry = "https://pypi.org/simple" } 207 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 208 | wheels = [ 209 | { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, 210 | { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, 211 | { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, 212 | { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, 213 | { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, 214 | { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, 215 | { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, 216 | { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, 217 | { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, 218 | { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, 219 | { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, 220 | { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, 221 | { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, 222 | { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, 223 | { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, 224 | { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, 225 | { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, 226 | { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, 227 | { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, 228 | { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, 229 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 230 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 231 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 232 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 233 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 234 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 235 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 236 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 237 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 238 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 239 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 240 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 241 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 242 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 243 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 244 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 245 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 246 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 247 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 248 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 249 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 250 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 251 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 252 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 253 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 254 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 255 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 256 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 257 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 258 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 259 | { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344 }, 260 | { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389 }, 261 | { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607 }, 262 | { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728 }, 263 | { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826 }, 264 | { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843 }, 265 | { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219 }, 266 | { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946 }, 267 | { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063 }, 268 | { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 }, 269 | ] 270 | 271 | [[package]] 272 | name = "mergedeep" 273 | version = "1.3.4" 274 | source = { registry = "https://pypi.org/simple" } 275 | sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } 276 | wheels = [ 277 | { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, 278 | ] 279 | 280 | [[package]] 281 | name = "mkdocs" 282 | version = "1.6.1" 283 | source = { registry = "https://pypi.org/simple" } 284 | dependencies = [ 285 | { name = "click" }, 286 | { name = "colorama", marker = "platform_system == 'Windows'" }, 287 | { name = "ghp-import" }, 288 | { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, 289 | { name = "jinja2" }, 290 | { name = "markdown" }, 291 | { name = "markupsafe" }, 292 | { name = "mergedeep" }, 293 | { name = "mkdocs-get-deps" }, 294 | { name = "packaging" }, 295 | { name = "pathspec" }, 296 | { name = "pyyaml" }, 297 | { name = "pyyaml-env-tag" }, 298 | { name = "watchdog" }, 299 | ] 300 | sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } 301 | wheels = [ 302 | { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, 303 | ] 304 | 305 | [[package]] 306 | name = "mkdocs-get-deps" 307 | version = "0.2.0" 308 | source = { registry = "https://pypi.org/simple" } 309 | dependencies = [ 310 | { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, 311 | { name = "mergedeep" }, 312 | { name = "platformdirs" }, 313 | { name = "pyyaml" }, 314 | ] 315 | sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } 316 | wheels = [ 317 | { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, 318 | ] 319 | 320 | [[package]] 321 | name = "mkdocs-material" 322 | version = "9.5.42" 323 | source = { registry = "https://pypi.org/simple" } 324 | dependencies = [ 325 | { name = "babel" }, 326 | { name = "colorama" }, 327 | { name = "jinja2" }, 328 | { name = "markdown" }, 329 | { name = "mkdocs" }, 330 | { name = "mkdocs-material-extensions" }, 331 | { name = "paginate" }, 332 | { name = "pygments" }, 333 | { name = "pymdown-extensions" }, 334 | { name = "regex" }, 335 | { name = "requests" }, 336 | ] 337 | sdist = { url = "https://files.pythonhosted.org/packages/f9/33/b3343ed975fbbd6798b8d8a7c4f1bf8489cc321fc8fd426eba3d87b0242e/mkdocs_material-9.5.42.tar.gz", hash = "sha256:92779b5e9b5934540c574c11647131d217dc540dce72b05feeda088c8eb1b8f2", size = 3963891 } 338 | wheels = [ 339 | { url = "https://files.pythonhosted.org/packages/55/55/ad3e6a60ac1e8e76025543c49c1f24ecd80fb38e8a57000403bf2f0a4293/mkdocs_material-9.5.42-py3-none-any.whl", hash = "sha256:452a7c5d21284b373f36b981a2cbebfff59263feebeede1bc28652e9c5bbe316", size = 8672619 }, 340 | ] 341 | 342 | [[package]] 343 | name = "mkdocs-material-extensions" 344 | version = "1.3.1" 345 | source = { registry = "https://pypi.org/simple" } 346 | sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } 347 | wheels = [ 348 | { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, 349 | ] 350 | 351 | [[package]] 352 | name = "packaging" 353 | version = "24.1" 354 | source = { registry = "https://pypi.org/simple" } 355 | sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } 356 | wheels = [ 357 | { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, 358 | ] 359 | 360 | [[package]] 361 | name = "paginate" 362 | version = "0.5.7" 363 | source = { registry = "https://pypi.org/simple" } 364 | sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } 365 | wheels = [ 366 | { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, 367 | ] 368 | 369 | [[package]] 370 | name = "pathspec" 371 | version = "0.12.1" 372 | source = { registry = "https://pypi.org/simple" } 373 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 374 | wheels = [ 375 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, 376 | ] 377 | 378 | [[package]] 379 | name = "platformdirs" 380 | version = "4.3.6" 381 | source = { registry = "https://pypi.org/simple" } 382 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 383 | wheels = [ 384 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 385 | ] 386 | 387 | [[package]] 388 | name = "pygments" 389 | version = "2.18.0" 390 | source = { registry = "https://pypi.org/simple" } 391 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } 392 | wheels = [ 393 | { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, 394 | ] 395 | 396 | [[package]] 397 | name = "pymdown-extensions" 398 | version = "10.11.2" 399 | source = { registry = "https://pypi.org/simple" } 400 | dependencies = [ 401 | { name = "markdown" }, 402 | { name = "pyyaml" }, 403 | ] 404 | sdist = { url = "https://files.pythonhosted.org/packages/f4/71/2730a20e9e3752393d78998347f8b1085ef9c417646ea9befbeef221e3c4/pymdown_extensions-10.11.2.tar.gz", hash = "sha256:bc8847ecc9e784a098efd35e20cba772bc5a1b529dfcef9dc1972db9021a1049", size = 830241 } 405 | wheels = [ 406 | { url = "https://files.pythonhosted.org/packages/c2/35/c0edf199257ef0a7d407d29cd51c4e70d1dad4370a5f44deb65a7a5475e2/pymdown_extensions-10.11.2-py3-none-any.whl", hash = "sha256:41cdde0a77290e480cf53892f5c5e50921a7ee3e5cd60ba91bf19837b33badcf", size = 259044 }, 407 | ] 408 | 409 | [[package]] 410 | name = "python-dateutil" 411 | version = "2.9.0.post0" 412 | source = { registry = "https://pypi.org/simple" } 413 | dependencies = [ 414 | { name = "six" }, 415 | ] 416 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 417 | wheels = [ 418 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, 419 | ] 420 | 421 | [[package]] 422 | name = "pyyaml" 423 | version = "6.0.2" 424 | source = { registry = "https://pypi.org/simple" } 425 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 426 | wheels = [ 427 | { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, 428 | { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, 429 | { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, 430 | { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, 431 | { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, 432 | { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, 433 | { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, 434 | { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, 435 | { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, 436 | { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, 437 | { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, 438 | { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, 439 | { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, 440 | { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, 441 | { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, 442 | { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, 443 | { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, 444 | { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, 445 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 446 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 447 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 448 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 449 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 450 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 451 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 452 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 453 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 454 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 455 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 456 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 457 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 458 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 459 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 460 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 461 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 462 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 463 | { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, 464 | { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, 465 | { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, 466 | { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, 467 | { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, 468 | { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, 469 | { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, 470 | { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, 471 | { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, 472 | ] 473 | 474 | [[package]] 475 | name = "pyyaml-env-tag" 476 | version = "0.1" 477 | source = { registry = "https://pypi.org/simple" } 478 | dependencies = [ 479 | { name = "pyyaml" }, 480 | ] 481 | sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } 482 | wheels = [ 483 | { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, 484 | ] 485 | 486 | [[package]] 487 | name = "regex" 488 | version = "2024.9.11" 489 | source = { registry = "https://pypi.org/simple" } 490 | sdist = { url = "https://files.pythonhosted.org/packages/f9/38/148df33b4dbca3bd069b963acab5e0fa1a9dbd6820f8c322d0dd6faeff96/regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", size = 399403 } 491 | wheels = [ 492 | { url = "https://files.pythonhosted.org/packages/63/12/497bd6599ce8a239ade68678132296aec5ee25ebea45fc8ba91aa60fceec/regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408", size = 482488 }, 493 | { url = "https://files.pythonhosted.org/packages/c1/24/595ddb9bec2a9b151cdaf9565b0c9f3da9f0cb1dca6c158bc5175332ddf8/regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d", size = 287443 }, 494 | { url = "https://files.pythonhosted.org/packages/69/a8/b2fb45d9715b1469383a0da7968f8cacc2f83e9fbbcd6b8713752dd980a6/regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5", size = 284561 }, 495 | { url = "https://files.pythonhosted.org/packages/88/87/1ce4a5357216b19b7055e7d3b0efc75a6e426133bf1e7d094321df514257/regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c", size = 783177 }, 496 | { url = "https://files.pythonhosted.org/packages/3c/65/b9f002ab32f7b68e7d1dcabb67926f3f47325b8dbc22cc50b6a043e1d07c/regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8", size = 823193 }, 497 | { url = "https://files.pythonhosted.org/packages/22/91/8339dd3abce101204d246e31bc26cdd7ec07c9f91598472459a3a902aa41/regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35", size = 809950 }, 498 | { url = "https://files.pythonhosted.org/packages/cb/19/556638aa11c2ec9968a1da998f07f27ec0abb9bf3c647d7c7985ca0b8eea/regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71", size = 782661 }, 499 | { url = "https://files.pythonhosted.org/packages/d1/e9/7a5bc4c6ef8d9cd2bdd83a667888fc35320da96a4cc4da5fa084330f53db/regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8", size = 772348 }, 500 | { url = "https://files.pythonhosted.org/packages/f1/0b/29f2105bfac3ed08e704914c38e93b07c784a6655f8a015297ee7173e95b/regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a", size = 697460 }, 501 | { url = "https://files.pythonhosted.org/packages/71/3a/52ff61054d15a4722605f5872ad03962b319a04c1ebaebe570b8b9b7dde1/regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d", size = 769151 }, 502 | { url = "https://files.pythonhosted.org/packages/97/07/37e460ab5ca84be8e1e197c3b526c5c86993dcc9e13cbc805c35fc2463c1/regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137", size = 777478 }, 503 | { url = "https://files.pythonhosted.org/packages/65/7b/953075723dd5ab00780043ac2f9de667306ff9e2a85332975e9f19279174/regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6", size = 845373 }, 504 | { url = "https://files.pythonhosted.org/packages/40/b8/3e9484c6230b8b6e8f816ab7c9a080e631124991a4ae2c27a81631777db0/regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca", size = 845369 }, 505 | { url = "https://files.pythonhosted.org/packages/b7/99/38434984d912edbd2e1969d116257e869578f67461bd7462b894c45ed874/regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a", size = 773935 }, 506 | { url = "https://files.pythonhosted.org/packages/ab/67/43174d2b46fa947b7b9dfe56b6c8a8a76d44223f35b1d64645a732fd1d6f/regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0", size = 261624 }, 507 | { url = "https://files.pythonhosted.org/packages/c4/2a/4f9c47d9395b6aff24874c761d8d620c0232f97c43ef3cf668c8b355e7a7/regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623", size = 274020 }, 508 | { url = "https://files.pythonhosted.org/packages/86/a1/d526b7b6095a0019aa360948c143aacfeb029919c898701ce7763bbe4c15/regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", size = 482483 }, 509 | { url = "https://files.pythonhosted.org/packages/32/d9/bfdd153179867c275719e381e1e8e84a97bd186740456a0dcb3e7125c205/regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", size = 287442 }, 510 | { url = "https://files.pythonhosted.org/packages/33/c4/60f3370735135e3a8d673ddcdb2507a8560d0e759e1398d366e43d000253/regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", size = 284561 }, 511 | { url = "https://files.pythonhosted.org/packages/b1/51/91a5ebdff17f9ec4973cb0aa9d37635efec1c6868654bbc25d1543aca4ec/regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", size = 791779 }, 512 | { url = "https://files.pythonhosted.org/packages/07/4a/022c5e6f0891a90cd7eb3d664d6c58ce2aba48bff107b00013f3d6167069/regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", size = 832605 }, 513 | { url = "https://files.pythonhosted.org/packages/ac/1c/3793990c8c83ca04e018151ddda83b83ecc41d89964f0f17749f027fc44d/regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", size = 818556 }, 514 | { url = "https://files.pythonhosted.org/packages/e9/5c/8b385afbfacb853730682c57be56225f9fe275c5bf02ac1fc88edbff316d/regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", size = 792808 }, 515 | { url = "https://files.pythonhosted.org/packages/9b/8b/a4723a838b53c771e9240951adde6af58c829fb6a6a28f554e8131f53839/regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", size = 781115 }, 516 | { url = "https://files.pythonhosted.org/packages/83/5f/031a04b6017033d65b261259c09043c06f4ef2d4eac841d0649d76d69541/regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", size = 778155 }, 517 | { url = "https://files.pythonhosted.org/packages/fd/cd/4660756070b03ce4a66663a43f6c6e7ebc2266cc6b4c586c167917185eb4/regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", size = 784614 }, 518 | { url = "https://files.pythonhosted.org/packages/93/8d/65b9bea7df120a7be8337c415b6d256ba786cbc9107cebba3bf8ff09da99/regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", size = 853744 }, 519 | { url = "https://files.pythonhosted.org/packages/96/a7/fba1eae75eb53a704475baf11bd44b3e6ccb95b316955027eb7748f24ef8/regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", size = 855890 }, 520 | { url = "https://files.pythonhosted.org/packages/45/14/d864b2db80a1a3358534392373e8a281d95b28c29c87d8548aed58813910/regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", size = 781887 }, 521 | { url = "https://files.pythonhosted.org/packages/4d/a9/bfb29b3de3eb11dc9b412603437023b8e6c02fb4e11311863d9bf62c403a/regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", size = 261644 }, 522 | { url = "https://files.pythonhosted.org/packages/c7/ab/1ad2511cf6a208fde57fafe49829cab8ca018128ab0d0b48973d8218634a/regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", size = 274033 }, 523 | { url = "https://files.pythonhosted.org/packages/6e/92/407531450762bed778eedbde04407f68cbd75d13cee96c6f8d6903d9c6c1/regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", size = 483590 }, 524 | { url = "https://files.pythonhosted.org/packages/8e/a2/048acbc5ae1f615adc6cba36cc45734e679b5f1e4e58c3c77f0ed611d4e2/regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", size = 288175 }, 525 | { url = "https://files.pythonhosted.org/packages/8a/ea/909d8620329ab710dfaf7b4adee41242ab7c9b95ea8d838e9bfe76244259/regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", size = 284749 }, 526 | { url = "https://files.pythonhosted.org/packages/ca/fa/521eb683b916389b4975337873e66954e0f6d8f91bd5774164a57b503185/regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", size = 795181 }, 527 | { url = "https://files.pythonhosted.org/packages/28/db/63047feddc3280cc242f9c74f7aeddc6ee662b1835f00046f57d5630c827/regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", size = 835842 }, 528 | { url = "https://files.pythonhosted.org/packages/e3/94/86adc259ff8ec26edf35fcca7e334566c1805c7493b192cb09679f9c3dee/regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", size = 823533 }, 529 | { url = "https://files.pythonhosted.org/packages/29/52/84662b6636061277cb857f658518aa7db6672bc6d1a3f503ccd5aefc581e/regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", size = 797037 }, 530 | { url = "https://files.pythonhosted.org/packages/c3/2a/cd4675dd987e4a7505f0364a958bc41f3b84942de9efaad0ef9a2646681c/regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", size = 784106 }, 531 | { url = "https://files.pythonhosted.org/packages/6f/75/3ea7ec29de0bbf42f21f812f48781d41e627d57a634f3f23947c9a46e303/regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", size = 782468 }, 532 | { url = "https://files.pythonhosted.org/packages/d3/67/15519d69b52c252b270e679cb578e22e0c02b8dd4e361f2b04efcc7f2335/regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", size = 790324 }, 533 | { url = "https://files.pythonhosted.org/packages/9c/71/eff77d3fe7ba08ab0672920059ec30d63fa7e41aa0fb61c562726e9bd721/regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", size = 860214 }, 534 | { url = "https://files.pythonhosted.org/packages/81/11/e1bdf84a72372e56f1ea4b833dd583b822a23138a616ace7ab57a0e11556/regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", size = 859420 }, 535 | { url = "https://files.pythonhosted.org/packages/ea/75/9753e9dcebfa7c3645563ef5c8a58f3a47e799c872165f37c55737dadd3e/regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", size = 787333 }, 536 | { url = "https://files.pythonhosted.org/packages/bc/4e/ba1cbca93141f7416624b3ae63573e785d4bc1834c8be44a8f0747919eca/regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", size = 262058 }, 537 | { url = "https://files.pythonhosted.org/packages/6e/16/efc5f194778bf43e5888209e5cec4b258005d37c613b67ae137df3b89c53/regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", size = 273526 }, 538 | { url = "https://files.pythonhosted.org/packages/93/0a/d1c6b9af1ff1e36832fe38d74d5c5bab913f2bdcbbd6bc0e7f3ce8b2f577/regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", size = 483376 }, 539 | { url = "https://files.pythonhosted.org/packages/a4/42/5910a050c105d7f750a72dcb49c30220c3ae4e2654e54aaaa0e9bc0584cb/regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", size = 288112 }, 540 | { url = "https://files.pythonhosted.org/packages/8d/56/0c262aff0e9224fa7ffce47b5458d373f4d3e3ff84e99b5ff0cb15e0b5b2/regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", size = 284608 }, 541 | { url = "https://files.pythonhosted.org/packages/b9/54/9fe8f9aec5007bbbbce28ba3d2e3eaca425f95387b7d1e84f0d137d25237/regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", size = 795337 }, 542 | { url = "https://files.pythonhosted.org/packages/b2/e7/6b2f642c3cded271c4f16cc4daa7231be544d30fe2b168e0223724b49a61/regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", size = 835848 }, 543 | { url = "https://files.pythonhosted.org/packages/cd/9e/187363bdf5d8c0e4662117b92aa32bf52f8f09620ae93abc7537d96d3311/regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", size = 823503 }, 544 | { url = "https://files.pythonhosted.org/packages/f8/10/601303b8ee93589f879664b0cfd3127949ff32b17f9b6c490fb201106c4d/regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", size = 797049 }, 545 | { url = "https://files.pythonhosted.org/packages/ef/1c/ea200f61ce9f341763f2717ab4daebe4422d83e9fd4ac5e33435fd3a148d/regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", size = 784144 }, 546 | { url = "https://files.pythonhosted.org/packages/d8/5c/d2429be49ef3292def7688401d3deb11702c13dcaecdc71d2b407421275b/regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", size = 782483 }, 547 | { url = "https://files.pythonhosted.org/packages/12/d9/cbc30f2ff7164f3b26a7760f87c54bf8b2faed286f60efd80350a51c5b99/regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", size = 790320 }, 548 | { url = "https://files.pythonhosted.org/packages/19/1d/43ed03a236313639da5a45e61bc553c8d41e925bcf29b0f8ecff0c2c3f25/regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", size = 860435 }, 549 | { url = "https://files.pythonhosted.org/packages/34/4f/5d04da61c7c56e785058a46349f7285ae3ebc0726c6ea7c5c70600a52233/regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", size = 859571 }, 550 | { url = "https://files.pythonhosted.org/packages/12/7f/8398c8155a3c70703a8e91c29532558186558e1aea44144b382faa2a6f7a/regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", size = 787398 }, 551 | { url = "https://files.pythonhosted.org/packages/58/3a/f5903977647a9a7e46d5535e9e96c194304aeeca7501240509bde2f9e17f/regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", size = 262035 }, 552 | { url = "https://files.pythonhosted.org/packages/ff/80/51ba3a4b7482f6011095b3a036e07374f64de180b7d870b704ed22509002/regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", size = 273510 }, 553 | { url = "https://files.pythonhosted.org/packages/a1/aa/e31baf8482ad690ccb3cdf20d1963a01e98d137e4d9ee493dbb0fa8ba2c6/regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066", size = 482489 }, 554 | { url = "https://files.pythonhosted.org/packages/a1/b5/449c2f14fc20dc42ef9729469fcff42809393470f021ed6c6fcf5f3d3297/regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62", size = 287440 }, 555 | { url = "https://files.pythonhosted.org/packages/3f/36/4b60a0c2e4cc6ecb2651be828117a31f42fae55a51a484a8071729df56a6/regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16", size = 284566 }, 556 | { url = "https://files.pythonhosted.org/packages/b4/21/feaa5b0d3e5e3bad659cd7d640e6b76cc0719504dbd9bc8f67cfa21bde82/regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3", size = 782747 }, 557 | { url = "https://files.pythonhosted.org/packages/bb/89/93516f0aa3e8a9366df2cf79bb0290abdc7dbe5dd27373d9bea0978b7ba6/regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199", size = 822700 }, 558 | { url = "https://files.pythonhosted.org/packages/d5/e7/79c04ccb81cee2831d9d4499274919b9153c1741ce8b3421d69cb0032f1b/regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8", size = 809327 }, 559 | { url = "https://files.pythonhosted.org/packages/01/e6/a7256c99c312b68f01cfd4f8eae6e770906fffb3832ecb66f35ca5b86b96/regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca", size = 781970 }, 560 | { url = "https://files.pythonhosted.org/packages/18/c4/29e8b6ff2208775858b5d4a2caa6428d40b5fade95aee426de7e42ffff39/regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9", size = 771885 }, 561 | { url = "https://files.pythonhosted.org/packages/95/78/7acd8882ac335f1f5ae1756417739fda3053e0bcacea8716ae4a04e74553/regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a", size = 696978 }, 562 | { url = "https://files.pythonhosted.org/packages/cb/d2/1d44f9b4a3d33ff5773fd79bea53e992d00f81e0af6f1f4e2efac1e4d897/regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39", size = 768655 }, 563 | { url = "https://files.pythonhosted.org/packages/79/ba/92ef9d3b8f59cb3df9febef07098dfb4a43c3bdcf35b1084c2009b0a93bf/regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba", size = 776922 }, 564 | { url = "https://files.pythonhosted.org/packages/16/71/d964c0c9d447f04bbe6ab5eafd220208e7d52b9608e452e6fcad553b38e0/regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664", size = 845014 }, 565 | { url = "https://files.pythonhosted.org/packages/83/cb/a378cdc2468782eefefa50183bbeabc3357fb588d4109d845f0a56e68713/regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89", size = 844916 }, 566 | { url = "https://files.pythonhosted.org/packages/b9/f0/82ea1565a6639270cfe96263002b3d91084a1db5048d9b6084f83bd5972d/regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35", size = 773409 }, 567 | { url = "https://files.pythonhosted.org/packages/97/9e/0400d742b9647b4940609a96d550de89e4e89c85f6a370796dab25b5979c/regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142", size = 261680 }, 568 | { url = "https://files.pythonhosted.org/packages/b6/f1/aef1112652ac7b3922d2c129f8325a4fd286b66691127dd99f380f8ede19/regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919", size = 274066 }, 569 | ] 570 | 571 | [[package]] 572 | name = "requests" 573 | version = "2.32.3" 574 | source = { registry = "https://pypi.org/simple" } 575 | dependencies = [ 576 | { name = "certifi" }, 577 | { name = "charset-normalizer" }, 578 | { name = "idna" }, 579 | { name = "urllib3" }, 580 | ] 581 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 582 | wheels = [ 583 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 584 | ] 585 | 586 | [[package]] 587 | name = "six" 588 | version = "1.16.0" 589 | source = { registry = "https://pypi.org/simple" } 590 | sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } 591 | wheels = [ 592 | { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, 593 | ] 594 | 595 | [[package]] 596 | name = "urllib3" 597 | version = "2.2.3" 598 | source = { registry = "https://pypi.org/simple" } 599 | sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } 600 | wheels = [ 601 | { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, 602 | ] 603 | 604 | [[package]] 605 | name = "watchdog" 606 | version = "5.0.3" 607 | source = { registry = "https://pypi.org/simple" } 608 | sdist = { url = "https://files.pythonhosted.org/packages/a2/48/a86139aaeab2db0a2482676f64798d8ac4d2dbb457523f50ab37bf02ce2c/watchdog-5.0.3.tar.gz", hash = "sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176", size = 129556 } 609 | wheels = [ 610 | { url = "https://files.pythonhosted.org/packages/05/2b/dd2081aab6fc9e785c2eee7146d3c6de58e607f4e70049d715cd170cbf77/watchdog-5.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:85527b882f3facda0579bce9d743ff7f10c3e1e0db0a0d0e28170a7d0e5ce2ea", size = 96652 }, 611 | { url = "https://files.pythonhosted.org/packages/9e/4f/f643c0a720d16ef7316aea06a79b96e229e59df4e0d83bec5e12713c1f29/watchdog-5.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:53adf73dcdc0ef04f7735066b4a57a4cd3e49ef135daae41d77395f0b5b692cb", size = 88651 }, 612 | { url = "https://files.pythonhosted.org/packages/2b/72/acb22067d1f18161914c9b1087c703d63638131a9fde78090da290663407/watchdog-5.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e25adddab85f674acac303cf1f5835951345a56c5f7f582987d266679979c75b", size = 89289 }, 613 | { url = "https://files.pythonhosted.org/packages/70/34/946f08602f8b8e6af45bc725e4a8013975a34883ab5570bd0d827a4c9829/watchdog-5.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f01f4a3565a387080dc49bdd1fefe4ecc77f894991b88ef927edbfa45eb10818", size = 96650 }, 614 | { url = "https://files.pythonhosted.org/packages/96/2b/b84e35d49e8b0bad77e5d086fc1e2c6c833bbfe74d53144cfe8b26117eff/watchdog-5.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91b522adc25614cdeaf91f7897800b82c13b4b8ac68a42ca959f992f6990c490", size = 88653 }, 615 | { url = "https://files.pythonhosted.org/packages/d5/3f/41b5d77c10f450b79921c17b7d0b416616048867bfe63acaa072a619a0cb/watchdog-5.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d52db5beb5e476e6853da2e2d24dbbbed6797b449c8bf7ea118a4ee0d2c9040e", size = 89286 }, 616 | { url = "https://files.pythonhosted.org/packages/1c/9b/8b206a928c188fdeb7b12e1c795199534cd44bdef223b8470129016009dd/watchdog-5.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8", size = 96739 }, 617 | { url = "https://files.pythonhosted.org/packages/e1/26/129ca9cd0f8016672f37000010c2fedc0b86816e894ebdc0af9bb04a6439/watchdog-5.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926", size = 88708 }, 618 | { url = "https://files.pythonhosted.org/packages/8f/b3/5e10ec32f0c429cdb55b1369066d6e83faf9985b3a53a4e37bb5c5e29aa0/watchdog-5.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e", size = 89309 }, 619 | { url = "https://files.pythonhosted.org/packages/54/c4/49af4ab00bcfb688e9962eace2edda07a2cf89b9699ea536da48e8585cff/watchdog-5.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:950f531ec6e03696a2414b6308f5c6ff9dab7821a768c9d5788b1314e9a46ca7", size = 96740 }, 620 | { url = "https://files.pythonhosted.org/packages/96/a4/b24de77cc9ae424c1687c9d4fb15aa560d7d7b28ba559aca72f781d0202b/watchdog-5.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6deb336cba5d71476caa029ceb6e88047fc1dc74b62b7c4012639c0b563906", size = 88711 }, 621 | { url = "https://files.pythonhosted.org/packages/a4/71/3f2e9fe8403386b99d788868955b3a790f7a09721501a7e1eb58f514ffaa/watchdog-5.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1021223c08ba8d2d38d71ec1704496471ffd7be42cfb26b87cd5059323a389a1", size = 89319 }, 622 | { url = "https://files.pythonhosted.org/packages/51/84/fc0b390012be6c4884d02bbef28d599afc6eeec4b560820bec98ff156fd1/watchdog-5.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:752fb40efc7cc8d88ebc332b8f4bcbe2b5cc7e881bccfeb8e25054c00c994ee3", size = 96649 }, 623 | { url = "https://files.pythonhosted.org/packages/e7/c5/5393fd01610a92bb7b291b742daae8d1b89f8e11bfb2c37361e17e1be1b8/watchdog-5.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2e8f3f955d68471fa37b0e3add18500790d129cc7efe89971b8a4cc6fdeb0b2", size = 88647 }, 624 | { url = "https://files.pythonhosted.org/packages/95/27/1eef63ba7015132a971e0304675497783faaf1ccb4f8223b06b9dfd90ba0/watchdog-5.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b8ca4d854adcf480bdfd80f46fdd6fb49f91dd020ae11c89b3a79e19454ec627", size = 89285 }, 625 | { url = "https://files.pythonhosted.org/packages/a2/d6/1d1ca81c75d903eca3fdb7061d93845485b58a5ba182d146843b88fc51c2/watchdog-5.0.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:90a67d7857adb1d985aca232cc9905dd5bc4803ed85cfcdcfcf707e52049eda7", size = 88172 }, 626 | { url = "https://files.pythonhosted.org/packages/47/bb/d5e0abcfd6d729029a24766682e062526db8b59e9ae0c94aff509e0fd2b9/watchdog-5.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:720ef9d3a4f9ca575a780af283c8fd3a0674b307651c1976714745090da5a9e8", size = 88644 }, 627 | { url = "https://files.pythonhosted.org/packages/8e/f6/0b9daa3398c3e2918fe79666540eedcbdc07614e77b2154cb35928d0c757/watchdog-5.0.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:223160bb359281bb8e31c8f1068bf71a6b16a8ad3d9524ca6f523ac666bb6a1e", size = 88173 }, 628 | { url = "https://files.pythonhosted.org/packages/27/c5/ce5bb0ce5587ce0899693be9fe20041e301fec143aae54066ac616a925b4/watchdog-5.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:560135542c91eaa74247a2e8430cf83c4342b29e8ad4f520ae14f0c8a19cfb5b", size = 88647 }, 629 | { url = "https://files.pythonhosted.org/packages/60/33/7cb71c9df9a77b6927ee5f48d25e1de5562ce0fa7e0c56dcf2b0472e64a2/watchdog-5.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91", size = 79335 }, 630 | { url = "https://files.pythonhosted.org/packages/f6/91/320bc1496cf951a3cf93a7ffd18a581f0792c304be963d943e0e608c2919/watchdog-5.0.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c", size = 79334 }, 631 | { url = "https://files.pythonhosted.org/packages/8b/2c/567c5e042ed667d3544c43d48a65cf853450a2d2a9089d9523a65f195e94/watchdog-5.0.3-py3-none-manylinux2014_i686.whl", hash = "sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c", size = 79333 }, 632 | { url = "https://files.pythonhosted.org/packages/c3/f0/64059fe162ef3274662e67bbdea6c45b3cd53e846d5bd1365fcdc3dc1d15/watchdog-5.0.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221", size = 79334 }, 633 | { url = "https://files.pythonhosted.org/packages/f6/d9/19b7d02965be2801e2d0f6f4bde23e4ae172620071b65430fa0c2f8441ac/watchdog-5.0.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05", size = 79333 }, 634 | { url = "https://files.pythonhosted.org/packages/cb/a1/5393ac6d0b095d3a44946b09258e9b5f22cb2fb67bcfa419dd868478826c/watchdog-5.0.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97", size = 79332 }, 635 | { url = "https://files.pythonhosted.org/packages/a0/58/edec25190b6403caf4426dd418234f2358a106634b7d6aa4aec6939b104f/watchdog-5.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7", size = 79334 }, 636 | { url = "https://files.pythonhosted.org/packages/97/69/cfb2d17ba8aabc73be2e2d03c8c319b1f32053a02c4b571852983aa24ff2/watchdog-5.0.3-py3-none-win32.whl", hash = "sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49", size = 79320 }, 637 | { url = "https://files.pythonhosted.org/packages/91/b4/2b5b59358dadfa2c8676322f955b6c22cde4937602f40490e2f7403e548e/watchdog-5.0.3-py3-none-win_amd64.whl", hash = "sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9", size = 79325 }, 638 | { url = "https://files.pythonhosted.org/packages/38/b8/0aa69337651b3005f161f7f494e59188a1d8d94171666900d26d29d10f69/watchdog-5.0.3-py3-none-win_ia64.whl", hash = "sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45", size = 79324 }, 639 | ] 640 | 641 | [[package]] 642 | name = "zipp" 643 | version = "3.20.2" 644 | source = { registry = "https://pypi.org/simple" } 645 | sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } 646 | wheels = [ 647 | { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, 648 | ] 649 | --------------------------------------------------------------------------------