Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER +1 -0
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA +203 -0
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD +54 -0
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/WHEEL +4 -0
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/entry_points.txt +2 -0
- lib/python3.12/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md +12 -0
- lib/python3.12/site-packages/markdown/__init__.py +48 -0
- lib/python3.12/site-packages/markdown/__main__.py +150 -0
- lib/python3.12/site-packages/markdown/__meta__.py +51 -0
- lib/python3.12/site-packages/markdown/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/__main__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/__meta__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/blockparser.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/blockprocessors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/core.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/htmlparser.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/inlinepatterns.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/postprocessors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/preprocessors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/serializers.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/test_tools.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/treeprocessors.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/__pycache__/util.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/blockparser.py +160 -0
- lib/python3.12/site-packages/markdown/blockprocessors.py +641 -0
- lib/python3.12/site-packages/markdown/core.py +503 -0
- lib/python3.12/site-packages/markdown/extensions/__init__.py +145 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/abbr.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/admonition.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/attr_list.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/codehilite.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/def_list.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/extra.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/fenced_code.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/footnotes.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_attrs.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_em.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/md_in_html.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/meta.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/nl2br.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/sane_lists.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/smarty.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/tables.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/toc.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/__pycache__/wikilinks.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/markdown/extensions/abbr.py +190 -0
- lib/python3.12/site-packages/markdown/extensions/admonition.py +184 -0
- lib/python3.12/site-packages/markdown/extensions/attr_list.py +204 -0
- lib/python3.12/site-packages/markdown/extensions/codehilite.py +347 -0
lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.3
|
| 2 |
+
Name: httpx
|
| 3 |
+
Version: 0.28.1
|
| 4 |
+
Summary: The next generation HTTP client.
|
| 5 |
+
Project-URL: Changelog, https://github.com/encode/httpx/blob/master/CHANGELOG.md
|
| 6 |
+
Project-URL: Documentation, https://www.python-httpx.org
|
| 7 |
+
Project-URL: Homepage, https://github.com/encode/httpx
|
| 8 |
+
Project-URL: Source, https://github.com/encode/httpx
|
| 9 |
+
Author-email: Tom Christie <tom@tomchristie.com>
|
| 10 |
+
License: BSD-3-Clause
|
| 11 |
+
Classifier: Development Status :: 4 - Beta
|
| 12 |
+
Classifier: Environment :: Web Environment
|
| 13 |
+
Classifier: Framework :: AsyncIO
|
| 14 |
+
Classifier: Framework :: Trio
|
| 15 |
+
Classifier: Intended Audience :: Developers
|
| 16 |
+
Classifier: License :: OSI Approved :: BSD License
|
| 17 |
+
Classifier: Operating System :: OS Independent
|
| 18 |
+
Classifier: Programming Language :: Python :: 3
|
| 19 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 25 |
+
Classifier: Topic :: Internet :: WWW/HTTP
|
| 26 |
+
Requires-Python: >=3.8
|
| 27 |
+
Requires-Dist: anyio
|
| 28 |
+
Requires-Dist: certifi
|
| 29 |
+
Requires-Dist: httpcore==1.*
|
| 30 |
+
Requires-Dist: idna
|
| 31 |
+
Provides-Extra: brotli
|
| 32 |
+
Requires-Dist: brotli; (platform_python_implementation == 'CPython') and extra == 'brotli'
|
| 33 |
+
Requires-Dist: brotlicffi; (platform_python_implementation != 'CPython') and extra == 'brotli'
|
| 34 |
+
Provides-Extra: cli
|
| 35 |
+
Requires-Dist: click==8.*; extra == 'cli'
|
| 36 |
+
Requires-Dist: pygments==2.*; extra == 'cli'
|
| 37 |
+
Requires-Dist: rich<14,>=10; extra == 'cli'
|
| 38 |
+
Provides-Extra: http2
|
| 39 |
+
Requires-Dist: h2<5,>=3; extra == 'http2'
|
| 40 |
+
Provides-Extra: socks
|
| 41 |
+
Requires-Dist: socksio==1.*; extra == 'socks'
|
| 42 |
+
Provides-Extra: zstd
|
| 43 |
+
Requires-Dist: zstandard>=0.18.0; extra == 'zstd'
|
| 44 |
+
Description-Content-Type: text/markdown
|
| 45 |
+
|
| 46 |
+
<p align="center">
|
| 47 |
+
<a href="https://www.python-httpx.org/"><img width="350" height="208" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/butterfly.png" alt='HTTPX'></a>
|
| 48 |
+
</p>
|
| 49 |
+
|
| 50 |
+
<p align="center"><strong>HTTPX</strong> <em>- A next-generation HTTP client for Python.</em></p>
|
| 51 |
+
|
| 52 |
+
<p align="center">
|
| 53 |
+
<a href="https://github.com/encode/httpx/actions">
|
| 54 |
+
<img src="https://github.com/encode/httpx/workflows/Test%20Suite/badge.svg" alt="Test Suite">
|
| 55 |
+
</a>
|
| 56 |
+
<a href="https://pypi.org/project/httpx/">
|
| 57 |
+
<img src="https://badge.fury.io/py/httpx.svg" alt="Package version">
|
| 58 |
+
</a>
|
| 59 |
+
</p>
|
| 60 |
+
|
| 61 |
+
HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
Install HTTPX using pip:
|
| 66 |
+
|
| 67 |
+
```shell
|
| 68 |
+
$ pip install httpx
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
Now, let's get started:
|
| 72 |
+
|
| 73 |
+
```pycon
|
| 74 |
+
>>> import httpx
|
| 75 |
+
>>> r = httpx.get('https://www.example.org/')
|
| 76 |
+
>>> r
|
| 77 |
+
<Response [200 OK]>
|
| 78 |
+
>>> r.status_code
|
| 79 |
+
200
|
| 80 |
+
>>> r.headers['content-type']
|
| 81 |
+
'text/html; charset=UTF-8'
|
| 82 |
+
>>> r.text
|
| 83 |
+
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
Or, using the command-line client.
|
| 87 |
+
|
| 88 |
+
```shell
|
| 89 |
+
$ pip install 'httpx[cli]' # The command line client is an optional dependency.
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
Which now allows us to use HTTPX directly from the command-line...
|
| 93 |
+
|
| 94 |
+
<p align="center">
|
| 95 |
+
<img width="700" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/httpx-help.png" alt='httpx --help'>
|
| 96 |
+
</p>
|
| 97 |
+
|
| 98 |
+
Sending a request...
|
| 99 |
+
|
| 100 |
+
<p align="center">
|
| 101 |
+
<img width="700" src="https://raw.githubusercontent.com/encode/httpx/master/docs/img/httpx-request.png" alt='httpx http://httpbin.org/json'>
|
| 102 |
+
</p>
|
| 103 |
+
|
| 104 |
+
## Features
|
| 105 |
+
|
| 106 |
+
HTTPX builds on the well-established usability of `requests`, and gives you:
|
| 107 |
+
|
| 108 |
+
* A broadly [requests-compatible API](https://www.python-httpx.org/compatibility/).
|
| 109 |
+
* An integrated command-line client.
|
| 110 |
+
* HTTP/1.1 [and HTTP/2 support](https://www.python-httpx.org/http2/).
|
| 111 |
+
* Standard synchronous interface, but with [async support if you need it](https://www.python-httpx.org/async/).
|
| 112 |
+
* Ability to make requests directly to [WSGI applications](https://www.python-httpx.org/advanced/transports/#wsgi-transport) or [ASGI applications](https://www.python-httpx.org/advanced/transports/#asgi-transport).
|
| 113 |
+
* Strict timeouts everywhere.
|
| 114 |
+
* Fully type annotated.
|
| 115 |
+
* 100% test coverage.
|
| 116 |
+
|
| 117 |
+
Plus all the standard features of `requests`...
|
| 118 |
+
|
| 119 |
+
* International Domains and URLs
|
| 120 |
+
* Keep-Alive & Connection Pooling
|
| 121 |
+
* Sessions with Cookie Persistence
|
| 122 |
+
* Browser-style SSL Verification
|
| 123 |
+
* Basic/Digest Authentication
|
| 124 |
+
* Elegant Key/Value Cookies
|
| 125 |
+
* Automatic Decompression
|
| 126 |
+
* Automatic Content Decoding
|
| 127 |
+
* Unicode Response Bodies
|
| 128 |
+
* Multipart File Uploads
|
| 129 |
+
* HTTP(S) Proxy Support
|
| 130 |
+
* Connection Timeouts
|
| 131 |
+
* Streaming Downloads
|
| 132 |
+
* .netrc Support
|
| 133 |
+
* Chunked Requests
|
| 134 |
+
|
| 135 |
+
## Installation
|
| 136 |
+
|
| 137 |
+
Install with pip:
|
| 138 |
+
|
| 139 |
+
```shell
|
| 140 |
+
$ pip install httpx
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
Or, to include the optional HTTP/2 support, use:
|
| 144 |
+
|
| 145 |
+
```shell
|
| 146 |
+
$ pip install httpx[http2]
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
HTTPX requires Python 3.8+.
|
| 150 |
+
|
| 151 |
+
## Documentation
|
| 152 |
+
|
| 153 |
+
Project documentation is available at [https://www.python-httpx.org/](https://www.python-httpx.org/).
|
| 154 |
+
|
| 155 |
+
For a run-through of all the basics, head over to the [QuickStart](https://www.python-httpx.org/quickstart/).
|
| 156 |
+
|
| 157 |
+
For more advanced topics, see the [Advanced Usage](https://www.python-httpx.org/advanced/) section, the [async support](https://www.python-httpx.org/async/) section, or the [HTTP/2](https://www.python-httpx.org/http2/) section.
|
| 158 |
+
|
| 159 |
+
The [Developer Interface](https://www.python-httpx.org/api/) provides a comprehensive API reference.
|
| 160 |
+
|
| 161 |
+
To find out about tools that integrate with HTTPX, see [Third Party Packages](https://www.python-httpx.org/third_party_packages/).
|
| 162 |
+
|
| 163 |
+
## Contribute
|
| 164 |
+
|
| 165 |
+
If you want to contribute with HTTPX check out the [Contributing Guide](https://www.python-httpx.org/contributing/) to learn how to start.
|
| 166 |
+
|
| 167 |
+
## Dependencies
|
| 168 |
+
|
| 169 |
+
The HTTPX project relies on these excellent libraries:
|
| 170 |
+
|
| 171 |
+
* `httpcore` - The underlying transport implementation for `httpx`.
|
| 172 |
+
* `h11` - HTTP/1.1 support.
|
| 173 |
+
* `certifi` - SSL certificates.
|
| 174 |
+
* `idna` - Internationalized domain name support.
|
| 175 |
+
* `sniffio` - Async library autodetection.
|
| 176 |
+
|
| 177 |
+
As well as these optional installs:
|
| 178 |
+
|
| 179 |
+
* `h2` - HTTP/2 support. *(Optional, with `httpx[http2]`)*
|
| 180 |
+
* `socksio` - SOCKS proxy support. *(Optional, with `httpx[socks]`)*
|
| 181 |
+
* `rich` - Rich terminal support. *(Optional, with `httpx[cli]`)*
|
| 182 |
+
* `click` - Command line client support. *(Optional, with `httpx[cli]`)*
|
| 183 |
+
* `brotli` or `brotlicffi` - Decoding for "brotli" compressed responses. *(Optional, with `httpx[brotli]`)*
|
| 184 |
+
* `zstandard` - Decoding for "zstd" compressed responses. *(Optional, with `httpx[zstd]`)*
|
| 185 |
+
|
| 186 |
+
A huge amount of credit is due to `requests` for the API layout that
|
| 187 |
+
much of this work follows, as well as to `urllib3` for plenty of design
|
| 188 |
+
inspiration around the lower-level networking details.
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
<p align="center"><i>HTTPX is <a href="https://github.com/encode/httpx/blob/master/LICENSE.md">BSD licensed</a> code.<br/>Designed & crafted with care.</i><br/>— 🦋 —</p>
|
| 193 |
+
|
| 194 |
+
## Release Information
|
| 195 |
+
|
| 196 |
+
### Fixed
|
| 197 |
+
|
| 198 |
+
* Reintroduced supposedly-private `URLTypes` shortcut. (#2673)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
[Full changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
|
lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
../../../bin/httpx,sha256=__uvRZOfKZGPYqahkhnsmchtxRnGtxgzZUmItZywotE,240
|
| 2 |
+
httpx-0.28.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 3 |
+
httpx-0.28.1.dist-info/METADATA,sha256=_rubD48-gNV8gZnDBPNcQzboWB0dGNeYPJJ2a4J5OyU,7052
|
| 4 |
+
httpx-0.28.1.dist-info/RECORD,,
|
| 5 |
+
httpx-0.28.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
| 6 |
+
httpx-0.28.1.dist-info/entry_points.txt,sha256=2lVkdQmxLA1pNMgSN2eV89o90HCZezhmNwsy6ryKDSA,37
|
| 7 |
+
httpx-0.28.1.dist-info/licenses/LICENSE.md,sha256=TsWdVE8StfU5o6cW_TIaxYzNgDC0ZSIfLIgCAM3yjY0,1508
|
| 8 |
+
httpx/__init__.py,sha256=CsaZe6yZj0rHg6322AWKWHGTMVr9txgEfD5P3_Rrz60,2171
|
| 9 |
+
httpx/__pycache__/__init__.cpython-312.pyc,,
|
| 10 |
+
httpx/__pycache__/__version__.cpython-312.pyc,,
|
| 11 |
+
httpx/__pycache__/_api.cpython-312.pyc,,
|
| 12 |
+
httpx/__pycache__/_auth.cpython-312.pyc,,
|
| 13 |
+
httpx/__pycache__/_client.cpython-312.pyc,,
|
| 14 |
+
httpx/__pycache__/_config.cpython-312.pyc,,
|
| 15 |
+
httpx/__pycache__/_content.cpython-312.pyc,,
|
| 16 |
+
httpx/__pycache__/_decoders.cpython-312.pyc,,
|
| 17 |
+
httpx/__pycache__/_exceptions.cpython-312.pyc,,
|
| 18 |
+
httpx/__pycache__/_main.cpython-312.pyc,,
|
| 19 |
+
httpx/__pycache__/_models.cpython-312.pyc,,
|
| 20 |
+
httpx/__pycache__/_multipart.cpython-312.pyc,,
|
| 21 |
+
httpx/__pycache__/_status_codes.cpython-312.pyc,,
|
| 22 |
+
httpx/__pycache__/_types.cpython-312.pyc,,
|
| 23 |
+
httpx/__pycache__/_urlparse.cpython-312.pyc,,
|
| 24 |
+
httpx/__pycache__/_urls.cpython-312.pyc,,
|
| 25 |
+
httpx/__pycache__/_utils.cpython-312.pyc,,
|
| 26 |
+
httpx/__version__.py,sha256=LoUyYeOXTieGzuP_64UL0wxdtxjuu_QbOvE7NOg-IqU,108
|
| 27 |
+
httpx/_api.py,sha256=r_Zgs4jIpcPJLqK5dbbSayqo_iVMKFaxZCd-oOHxLEs,11743
|
| 28 |
+
httpx/_auth.py,sha256=Yr3QwaUSK17rGYx-7j-FdicFIzz4Y9FFV-1F4-7RXX4,11891
|
| 29 |
+
httpx/_client.py,sha256=xD-UG67-WMkeltAAOeGGj-cZ2RRTAm19sWRxlFY7_40,65714
|
| 30 |
+
httpx/_config.py,sha256=pPp2U-wicfcKsF-KYRE1LYdt3e6ERGeIoXZ8Gjo3LWc,8547
|
| 31 |
+
httpx/_content.py,sha256=LGGzrJTR3OvN4Mb1GVVNLXkXJH-6oKlwAttO9p5w_yg,8161
|
| 32 |
+
httpx/_decoders.py,sha256=p0dX8I0NEHexs3UGp4SsZutiMhsXrrWl6-GnqVb0iKM,12041
|
| 33 |
+
httpx/_exceptions.py,sha256=bxW7fxzgVMAdNTbwT0Vnq04gJDW1_gI_GFiQPuMyjL0,8527
|
| 34 |
+
httpx/_main.py,sha256=Cg9GMabiTT_swaDfUgIRitSwxLRMSwUDOm7LdSGqlA4,15626
|
| 35 |
+
httpx/_models.py,sha256=4__Guyv1gLxuZChwim8kfQNiIOcJ9acreFOSurvZfms,44700
|
| 36 |
+
httpx/_multipart.py,sha256=KOHEZZl6oohg9mPaKyyu345qq1rJLg35TUG3YAzXB3Y,9843
|
| 37 |
+
httpx/_status_codes.py,sha256=DYn-2ufBgMeXy5s8x3_TB7wjAuAAMewTakPrm5rXEsc,5639
|
| 38 |
+
httpx/_transports/__init__.py,sha256=GbUoBSAOp7z-l-9j5YhMhR3DMIcn6FVLhj072O3Nnno,275
|
| 39 |
+
httpx/_transports/__pycache__/__init__.cpython-312.pyc,,
|
| 40 |
+
httpx/_transports/__pycache__/asgi.cpython-312.pyc,,
|
| 41 |
+
httpx/_transports/__pycache__/base.cpython-312.pyc,,
|
| 42 |
+
httpx/_transports/__pycache__/default.cpython-312.pyc,,
|
| 43 |
+
httpx/_transports/__pycache__/mock.cpython-312.pyc,,
|
| 44 |
+
httpx/_transports/__pycache__/wsgi.cpython-312.pyc,,
|
| 45 |
+
httpx/_transports/asgi.py,sha256=HRfiDYMPt4wQH2gFgHZg4c-i3sblo6bL5GTqcET-xz8,5501
|
| 46 |
+
httpx/_transports/base.py,sha256=kZS_VMbViYfF570pogUCJ1bulz-ybfL51Pqs9yktebU,2523
|
| 47 |
+
httpx/_transports/default.py,sha256=AzeaRUyVwCccTyyNJexDf0n1dFfzzydpdIQgvw7PLnk,13983
|
| 48 |
+
httpx/_transports/mock.py,sha256=PTo0d567RITXxGrki6kN7_67wwAxfwiMDcuXJiZCjEo,1232
|
| 49 |
+
httpx/_transports/wsgi.py,sha256=NcPX3Xap_EwCFZWO_OaSyQNuInCYx1QMNbO8GAei6jY,4825
|
| 50 |
+
httpx/_types.py,sha256=Jyh41GQq7AOev8IOWKDAg7zCbvHAfufmW5g_PiTtErY,2965
|
| 51 |
+
httpx/_urlparse.py,sha256=ZAmH47ONfkxrrj-PPYhGeiHjb6AjKCS-ANWIN4OL_KY,18546
|
| 52 |
+
httpx/_urls.py,sha256=dX99VR1DSOHpgo9Aq7PzYO4FKdxqKjwyNp8grf8dHN0,21550
|
| 53 |
+
httpx/_utils.py,sha256=_TVeqAKvxJkKHdz7dFeb4s0LZqQXgeFkXSgfiHBK_1o,8285
|
| 54 |
+
httpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
lib/python3.12/site-packages/httpx-0.28.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.26.3
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
lib/python3.12/site-packages/httpx-0.28.1.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
httpx = httpx:main
|
lib/python3.12/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/).
|
| 2 |
+
All rights reserved.
|
| 3 |
+
|
| 4 |
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
| 5 |
+
|
| 6 |
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
| 7 |
+
|
| 8 |
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
| 9 |
+
|
| 10 |
+
* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
| 11 |
+
|
| 12 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
lib/python3.12/site-packages/markdown/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# - Documentation: https://python-markdown.github.io/
|
| 6 |
+
# - GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# - PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# - Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# - Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# - Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
Python-Markdown provides two public functions ([`markdown.markdown`][] and [`markdown.markdownFromFile`][])
|
| 22 |
+
both of which wrap the public class [`markdown.Markdown`][]. All submodules support these public functions
|
| 23 |
+
and class and/or provide extension support.
|
| 24 |
+
|
| 25 |
+
Modules:
|
| 26 |
+
core: Core functionality.
|
| 27 |
+
preprocessors: Pre-processors.
|
| 28 |
+
blockparser: Core Markdown block parser.
|
| 29 |
+
blockprocessors: Block processors.
|
| 30 |
+
treeprocessors: Tree processors.
|
| 31 |
+
inlinepatterns: Inline patterns.
|
| 32 |
+
postprocessors: Post-processors.
|
| 33 |
+
serializers: Serializers.
|
| 34 |
+
util: Utility functions.
|
| 35 |
+
htmlparser: HTML parser.
|
| 36 |
+
test_tools: Testing utilities.
|
| 37 |
+
extensions: Markdown extensions.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
from __future__ import annotations
|
| 41 |
+
|
| 42 |
+
from .core import Markdown, markdown, markdownFromFile
|
| 43 |
+
from .__meta__ import __version__, __version_info__ # noqa
|
| 44 |
+
|
| 45 |
+
# For backward compatibility as some extensions expect it...
|
| 46 |
+
from .extensions import Extension # noqa
|
| 47 |
+
|
| 48 |
+
__all__ = ['Markdown', 'markdown', 'markdownFromFile']
|
lib/python3.12/site-packages/markdown/__main__.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import sys
|
| 23 |
+
import optparse
|
| 24 |
+
import warnings
|
| 25 |
+
import markdown
|
| 26 |
+
try:
|
| 27 |
+
# We use `unsafe_load` because users may need to pass in actual Python
|
| 28 |
+
# objects. As this is only available from the CLI, the user has much
|
| 29 |
+
# worse problems if an attacker can use this as an attach vector.
|
| 30 |
+
from yaml import unsafe_load as yaml_load
|
| 31 |
+
except ImportError: # pragma: no cover
|
| 32 |
+
try:
|
| 33 |
+
# Fall back to PyYAML <5.1
|
| 34 |
+
from yaml import load as yaml_load
|
| 35 |
+
except ImportError:
|
| 36 |
+
# Fall back to JSON
|
| 37 |
+
from json import load as yaml_load
|
| 38 |
+
|
| 39 |
+
import logging
|
| 40 |
+
from logging import DEBUG, WARNING, CRITICAL
|
| 41 |
+
|
| 42 |
+
logger = logging.getLogger('MARKDOWN')
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def parse_options(args=None, values=None):
|
| 46 |
+
"""
|
| 47 |
+
Define and parse `optparse` options for command-line usage.
|
| 48 |
+
"""
|
| 49 |
+
usage = """%prog [options] [INPUTFILE]
|
| 50 |
+
(STDIN is assumed if no INPUTFILE is given)"""
|
| 51 |
+
desc = "A Python implementation of John Gruber's Markdown. " \
|
| 52 |
+
"https://Python-Markdown.github.io/"
|
| 53 |
+
ver = "%%prog %s" % markdown.__version__
|
| 54 |
+
|
| 55 |
+
parser = optparse.OptionParser(usage=usage, description=desc, version=ver)
|
| 56 |
+
parser.add_option("-f", "--file", dest="filename", default=None,
|
| 57 |
+
help="Write output to OUTPUT_FILE. Defaults to STDOUT.",
|
| 58 |
+
metavar="OUTPUT_FILE")
|
| 59 |
+
parser.add_option("-e", "--encoding", dest="encoding",
|
| 60 |
+
help="Encoding for input and output files.",)
|
| 61 |
+
parser.add_option("-o", "--output_format", dest="output_format",
|
| 62 |
+
default='xhtml', metavar="OUTPUT_FORMAT",
|
| 63 |
+
help="Use output format 'xhtml' (default) or 'html'.")
|
| 64 |
+
parser.add_option("-n", "--no_lazy_ol", dest="lazy_ol",
|
| 65 |
+
action='store_false', default=True,
|
| 66 |
+
help="Observe number of first item of ordered lists.")
|
| 67 |
+
parser.add_option("-x", "--extension", action="append", dest="extensions",
|
| 68 |
+
help="Load extension EXTENSION.", metavar="EXTENSION")
|
| 69 |
+
parser.add_option("-c", "--extension_configs",
|
| 70 |
+
dest="configfile", default=None,
|
| 71 |
+
help="Read extension configurations from CONFIG_FILE. "
|
| 72 |
+
"CONFIG_FILE must be of JSON or YAML format. YAML "
|
| 73 |
+
"format requires that a python YAML library be "
|
| 74 |
+
"installed. The parsed JSON or YAML must result in a "
|
| 75 |
+
"python dictionary which would be accepted by the "
|
| 76 |
+
"'extension_configs' keyword on the markdown.Markdown "
|
| 77 |
+
"class. The extensions must also be loaded with the "
|
| 78 |
+
"`--extension` option.",
|
| 79 |
+
metavar="CONFIG_FILE")
|
| 80 |
+
parser.add_option("-q", "--quiet", default=CRITICAL,
|
| 81 |
+
action="store_const", const=CRITICAL+10, dest="verbose",
|
| 82 |
+
help="Suppress all warnings.")
|
| 83 |
+
parser.add_option("-v", "--verbose",
|
| 84 |
+
action="store_const", const=WARNING, dest="verbose",
|
| 85 |
+
help="Print all warnings.")
|
| 86 |
+
parser.add_option("--noisy",
|
| 87 |
+
action="store_const", const=DEBUG, dest="verbose",
|
| 88 |
+
help="Print debug messages.")
|
| 89 |
+
|
| 90 |
+
(options, args) = parser.parse_args(args, values)
|
| 91 |
+
|
| 92 |
+
if len(args) == 0:
|
| 93 |
+
input_file = None
|
| 94 |
+
else:
|
| 95 |
+
input_file = args[0]
|
| 96 |
+
|
| 97 |
+
if not options.extensions:
|
| 98 |
+
options.extensions = []
|
| 99 |
+
|
| 100 |
+
extension_configs = {}
|
| 101 |
+
if options.configfile:
|
| 102 |
+
with open(
|
| 103 |
+
options.configfile, mode="r", encoding=options.encoding
|
| 104 |
+
) as fp:
|
| 105 |
+
try:
|
| 106 |
+
extension_configs = yaml_load(fp)
|
| 107 |
+
except Exception as e:
|
| 108 |
+
message = "Failed parsing extension config file: %s" % \
|
| 109 |
+
options.configfile
|
| 110 |
+
e.args = (message,) + e.args[1:]
|
| 111 |
+
raise
|
| 112 |
+
|
| 113 |
+
opts = {
|
| 114 |
+
'input': input_file,
|
| 115 |
+
'output': options.filename,
|
| 116 |
+
'extensions': options.extensions,
|
| 117 |
+
'extension_configs': extension_configs,
|
| 118 |
+
'encoding': options.encoding,
|
| 119 |
+
'output_format': options.output_format,
|
| 120 |
+
'lazy_ol': options.lazy_ol
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
return opts, options.verbose
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def run(): # pragma: no cover
|
| 127 |
+
"""Run Markdown from the command line."""
|
| 128 |
+
|
| 129 |
+
# Parse options and adjust logging level if necessary
|
| 130 |
+
options, logging_level = parse_options()
|
| 131 |
+
if not options:
|
| 132 |
+
sys.exit(2)
|
| 133 |
+
logger.setLevel(logging_level)
|
| 134 |
+
console_handler = logging.StreamHandler()
|
| 135 |
+
logger.addHandler(console_handler)
|
| 136 |
+
if logging_level <= WARNING:
|
| 137 |
+
# Ensure deprecation warnings get displayed
|
| 138 |
+
warnings.filterwarnings('default')
|
| 139 |
+
logging.captureWarnings(True)
|
| 140 |
+
warn_logger = logging.getLogger('py.warnings')
|
| 141 |
+
warn_logger.addHandler(console_handler)
|
| 142 |
+
|
| 143 |
+
# Run
|
| 144 |
+
markdown.markdownFromFile(**options)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
if __name__ == '__main__': # pragma: no cover
|
| 148 |
+
# Support running module as a command line command.
|
| 149 |
+
# python -m markdown [options] [args]
|
| 150 |
+
run()
|
lib/python3.12/site-packages/markdown/__meta__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
# __version_info__ format:
|
| 21 |
+
# (major, minor, patch, dev/alpha/beta/rc/final, #)
|
| 22 |
+
# (1, 1, 2, 'dev', 0) => "1.1.2.dev0"
|
| 23 |
+
# (1, 1, 2, 'alpha', 1) => "1.1.2a1"
|
| 24 |
+
# (1, 2, 0, 'beta', 2) => "1.2b2"
|
| 25 |
+
# (1, 2, 0, 'rc', 4) => "1.2rc4"
|
| 26 |
+
# (1, 2, 0, 'final', 0) => "1.2"
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
__version_info__ = (3, 10, 1, 'final', 0)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _get_version(version_info):
|
| 35 |
+
" Returns a PEP 440-compliant version number from `version_info`. "
|
| 36 |
+
assert len(version_info) == 5
|
| 37 |
+
assert version_info[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
|
| 38 |
+
|
| 39 |
+
parts = 2 if version_info[2] == 0 else 3
|
| 40 |
+
v = '.'.join(map(str, version_info[:parts]))
|
| 41 |
+
|
| 42 |
+
if version_info[3] == 'dev':
|
| 43 |
+
v += '.dev' + str(version_info[4])
|
| 44 |
+
elif version_info[3] != 'final':
|
| 45 |
+
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
|
| 46 |
+
v += mapping[version_info[3]] + str(version_info[4])
|
| 47 |
+
|
| 48 |
+
return v
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
__version__ = _get_version(__version_info__)
|
lib/python3.12/site-packages/markdown/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.18 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/__main__.cpython-312.pyc
ADDED
|
Binary file (5.41 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/__meta__.cpython-312.pyc
ADDED
|
Binary file (1.14 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/blockparser.cpython-312.pyc
ADDED
|
Binary file (6.42 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/blockprocessors.cpython-312.pyc
ADDED
|
Binary file (30.4 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/core.cpython-312.pyc
ADDED
|
Binary file (22.4 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/htmlparser.cpython-312.pyc
ADDED
|
Binary file (19.7 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/inlinepatterns.cpython-312.pyc
ADDED
|
Binary file (45.4 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/postprocessors.cpython-312.pyc
ADDED
|
Binary file (6.29 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/preprocessors.cpython-312.pyc
ADDED
|
Binary file (4.02 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/serializers.cpython-312.pyc
ADDED
|
Binary file (6.34 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/test_tools.cpython-312.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/treeprocessors.cpython-312.pyc
ADDED
|
Binary file (20.9 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/__pycache__/util.cpython-312.pyc
ADDED
|
Binary file (16.1 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/blockparser.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
The block parser handles basic parsing of Markdown blocks. It doesn't concern
|
| 22 |
+
itself with inline elements such as `**bold**` or `*italics*`, but rather just
|
| 23 |
+
catches blocks, lists, quotes, etc.
|
| 24 |
+
|
| 25 |
+
The `BlockParser` is made up of a bunch of `BlockProcessors`, each handling a
|
| 26 |
+
different type of block. Extensions may add/replace/remove `BlockProcessors`
|
| 27 |
+
as they need to alter how Markdown blocks are parsed.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
from __future__ import annotations
|
| 31 |
+
|
| 32 |
+
import xml.etree.ElementTree as etree
|
| 33 |
+
from typing import TYPE_CHECKING, Iterable, Any
|
| 34 |
+
from . import util
|
| 35 |
+
|
| 36 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 37 |
+
from markdown import Markdown
|
| 38 |
+
from .blockprocessors import BlockProcessor
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class State(list):
|
| 42 |
+
""" Track the current and nested state of the parser.
|
| 43 |
+
|
| 44 |
+
This utility class is used to track the state of the `BlockParser` and
|
| 45 |
+
support multiple levels if nesting. It's just a simple API wrapped around
|
| 46 |
+
a list. Each time a state is set, that state is appended to the end of the
|
| 47 |
+
list. Each time a state is reset, that state is removed from the end of
|
| 48 |
+
the list.
|
| 49 |
+
|
| 50 |
+
Therefore, each time a state is set for a nested block, that state must be
|
| 51 |
+
reset when we back out of that level of nesting or the state could be
|
| 52 |
+
corrupted.
|
| 53 |
+
|
| 54 |
+
While all the methods of a list object are available, only the three
|
| 55 |
+
defined below need be used.
|
| 56 |
+
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def set(self, state: Any):
|
| 60 |
+
""" Set a new state. """
|
| 61 |
+
self.append(state)
|
| 62 |
+
|
| 63 |
+
def reset(self) -> None:
|
| 64 |
+
""" Step back one step in nested state. """
|
| 65 |
+
self.pop()
|
| 66 |
+
|
| 67 |
+
def isstate(self, state: Any) -> bool:
|
| 68 |
+
""" Test that top (current) level is of given state. """
|
| 69 |
+
if len(self):
|
| 70 |
+
return self[-1] == state
|
| 71 |
+
else:
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class BlockParser:
|
| 76 |
+
""" Parse Markdown blocks into an `ElementTree` object.
|
| 77 |
+
|
| 78 |
+
A wrapper class that stitches the various `BlockProcessors` together,
|
| 79 |
+
looping through them and creating an `ElementTree` object.
|
| 80 |
+
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
def __init__(self, md: Markdown):
|
| 84 |
+
""" Initialize the block parser.
|
| 85 |
+
|
| 86 |
+
Arguments:
|
| 87 |
+
md: A Markdown instance.
|
| 88 |
+
|
| 89 |
+
Attributes:
|
| 90 |
+
BlockParser.md (Markdown): A Markdown instance.
|
| 91 |
+
BlockParser.state (State): Tracks the nesting level of current location in document being parsed.
|
| 92 |
+
BlockParser.blockprocessors (util.Registry): A collection of
|
| 93 |
+
[`blockprocessors`][markdown.blockprocessors].
|
| 94 |
+
|
| 95 |
+
"""
|
| 96 |
+
self.blockprocessors: util.Registry[BlockProcessor] = util.Registry()
|
| 97 |
+
self.state = State()
|
| 98 |
+
self.md = md
|
| 99 |
+
|
| 100 |
+
def parseDocument(self, lines: Iterable[str]) -> etree.ElementTree:
|
| 101 |
+
""" Parse a Markdown document into an `ElementTree`.
|
| 102 |
+
|
| 103 |
+
Given a list of lines, an `ElementTree` object (not just a parent
|
| 104 |
+
`Element`) is created and the root element is passed to the parser
|
| 105 |
+
as the parent. The `ElementTree` object is returned.
|
| 106 |
+
|
| 107 |
+
This should only be called on an entire document, not pieces.
|
| 108 |
+
|
| 109 |
+
Arguments:
|
| 110 |
+
lines: A list of lines (strings).
|
| 111 |
+
|
| 112 |
+
Returns:
|
| 113 |
+
An element tree.
|
| 114 |
+
"""
|
| 115 |
+
# Create an `ElementTree` from the lines
|
| 116 |
+
self.root = etree.Element(self.md.doc_tag)
|
| 117 |
+
self.parseChunk(self.root, '\n'.join(lines))
|
| 118 |
+
return etree.ElementTree(self.root)
|
| 119 |
+
|
| 120 |
+
def parseChunk(self, parent: etree.Element, text: str) -> None:
|
| 121 |
+
""" Parse a chunk of Markdown text and attach to given `etree` node.
|
| 122 |
+
|
| 123 |
+
While the `text` argument is generally assumed to contain multiple
|
| 124 |
+
blocks which will be split on blank lines, it could contain only one
|
| 125 |
+
block. Generally, this method would be called by extensions when
|
| 126 |
+
block parsing is required.
|
| 127 |
+
|
| 128 |
+
The `parent` `etree` Element passed in is altered in place.
|
| 129 |
+
Nothing is returned.
|
| 130 |
+
|
| 131 |
+
Arguments:
|
| 132 |
+
parent: The parent element.
|
| 133 |
+
text: The text to parse.
|
| 134 |
+
|
| 135 |
+
"""
|
| 136 |
+
self.parseBlocks(parent, text.split('\n\n'))
|
| 137 |
+
|
| 138 |
+
def parseBlocks(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 139 |
+
""" Process blocks of Markdown text and attach to given `etree` node.
|
| 140 |
+
|
| 141 |
+
Given a list of `blocks`, each `blockprocessor` is stepped through
|
| 142 |
+
until there are no blocks left. While an extension could potentially
|
| 143 |
+
call this method directly, it's generally expected to be used
|
| 144 |
+
internally.
|
| 145 |
+
|
| 146 |
+
This is a public method as an extension may need to add/alter
|
| 147 |
+
additional `BlockProcessors` which call this method to recursively
|
| 148 |
+
parse a nested block.
|
| 149 |
+
|
| 150 |
+
Arguments:
|
| 151 |
+
parent: The parent element.
|
| 152 |
+
blocks: The blocks of text to parse.
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
while blocks:
|
| 156 |
+
for processor in self.blockprocessors:
|
| 157 |
+
if processor.test(parent, blocks[0]):
|
| 158 |
+
if processor.run(parent, blocks) is not False:
|
| 159 |
+
# run returns True or None
|
| 160 |
+
break
|
lib/python3.12/site-packages/markdown/blockprocessors.py
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
A block processor parses blocks of text and adds new elements to the ElementTree. Blocks of text,
|
| 22 |
+
separated from other text by blank lines, may have a different syntax and produce a differently
|
| 23 |
+
structured tree than other Markdown. Block processors excel at handling code formatting, equation
|
| 24 |
+
layouts, tables, etc.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
|
| 29 |
+
import logging
|
| 30 |
+
import re
|
| 31 |
+
import xml.etree.ElementTree as etree
|
| 32 |
+
from typing import TYPE_CHECKING, Any
|
| 33 |
+
from . import util
|
| 34 |
+
from .blockparser import BlockParser
|
| 35 |
+
|
| 36 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 37 |
+
from markdown import Markdown
|
| 38 |
+
|
| 39 |
+
logger = logging.getLogger('MARKDOWN')
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def build_block_parser(md: Markdown, **kwargs: Any) -> BlockParser:
|
| 43 |
+
""" Build the default block parser used by Markdown. """
|
| 44 |
+
parser = BlockParser(md)
|
| 45 |
+
parser.blockprocessors.register(EmptyBlockProcessor(parser), 'empty', 100)
|
| 46 |
+
parser.blockprocessors.register(ListIndentProcessor(parser), 'indent', 90)
|
| 47 |
+
parser.blockprocessors.register(CodeBlockProcessor(parser), 'code', 80)
|
| 48 |
+
parser.blockprocessors.register(HashHeaderProcessor(parser), 'hashheader', 70)
|
| 49 |
+
parser.blockprocessors.register(SetextHeaderProcessor(parser), 'setextheader', 60)
|
| 50 |
+
parser.blockprocessors.register(HRProcessor(parser), 'hr', 50)
|
| 51 |
+
parser.blockprocessors.register(OListProcessor(parser), 'olist', 40)
|
| 52 |
+
parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
|
| 53 |
+
parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
|
| 54 |
+
parser.blockprocessors.register(ReferenceProcessor(parser), 'reference', 15)
|
| 55 |
+
parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
|
| 56 |
+
return parser
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class BlockProcessor:
|
| 60 |
+
""" Base class for block processors.
|
| 61 |
+
|
| 62 |
+
Each subclass will provide the methods below to work with the source and
|
| 63 |
+
tree. Each processor will need to define it's own `test` and `run`
|
| 64 |
+
methods. The `test` method should return True or False, to indicate
|
| 65 |
+
whether the current block should be processed by this processor. If the
|
| 66 |
+
test passes, the parser will call the processors `run` method.
|
| 67 |
+
|
| 68 |
+
Attributes:
|
| 69 |
+
BlockProcessor.parser (BlockParser): The `BlockParser` instance this is attached to.
|
| 70 |
+
BlockProcessor.tab_length (int): The tab length set on the `Markdown` instance.
|
| 71 |
+
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
def __init__(self, parser: BlockParser):
|
| 75 |
+
self.parser = parser
|
| 76 |
+
self.tab_length = parser.md.tab_length
|
| 77 |
+
|
| 78 |
+
def lastChild(self, parent: etree.Element) -> etree.Element | None:
|
| 79 |
+
""" Return the last child of an `etree` element. """
|
| 80 |
+
if len(parent):
|
| 81 |
+
return parent[-1]
|
| 82 |
+
else:
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
def detab(self, text: str, length: int | None = None) -> tuple[str, str]:
|
| 86 |
+
""" Remove a tab from the front of each line of the given text. """
|
| 87 |
+
if length is None:
|
| 88 |
+
length = self.tab_length
|
| 89 |
+
newtext = []
|
| 90 |
+
lines = text.split('\n')
|
| 91 |
+
for line in lines:
|
| 92 |
+
if line.startswith(' ' * length):
|
| 93 |
+
newtext.append(line[length:])
|
| 94 |
+
elif not line.strip():
|
| 95 |
+
newtext.append('')
|
| 96 |
+
else:
|
| 97 |
+
break
|
| 98 |
+
return '\n'.join(newtext), '\n'.join(lines[len(newtext):])
|
| 99 |
+
|
| 100 |
+
def looseDetab(self, text: str, level: int = 1) -> str:
|
| 101 |
+
""" Remove a tab from front of lines but allowing dedented lines. """
|
| 102 |
+
lines = text.split('\n')
|
| 103 |
+
for i in range(len(lines)):
|
| 104 |
+
if lines[i].startswith(' '*self.tab_length*level):
|
| 105 |
+
lines[i] = lines[i][self.tab_length*level:]
|
| 106 |
+
return '\n'.join(lines)
|
| 107 |
+
|
| 108 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 109 |
+
""" Test for block type. Must be overridden by subclasses.
|
| 110 |
+
|
| 111 |
+
As the parser loops through processors, it will call the `test`
|
| 112 |
+
method on each to determine if the given block of text is of that
|
| 113 |
+
type. This method must return a boolean `True` or `False`. The
|
| 114 |
+
actual method of testing is left to the needs of that particular
|
| 115 |
+
block type. It could be as simple as `block.startswith(some_string)`
|
| 116 |
+
or a complex regular expression. As the block type may be different
|
| 117 |
+
depending on the parent of the block (i.e. inside a list), the parent
|
| 118 |
+
`etree` element is also provided and may be used as part of the test.
|
| 119 |
+
|
| 120 |
+
Keyword arguments:
|
| 121 |
+
parent: An `etree` element which will be the parent of the block.
|
| 122 |
+
block: A block of text from the source which has been split at blank lines.
|
| 123 |
+
"""
|
| 124 |
+
pass # pragma: no cover
|
| 125 |
+
|
| 126 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> bool | None:
|
| 127 |
+
""" Run processor. Must be overridden by subclasses.
|
| 128 |
+
|
| 129 |
+
When the parser determines the appropriate type of a block, the parser
|
| 130 |
+
will call the corresponding processor's `run` method. This method
|
| 131 |
+
should parse the individual lines of the block and append them to
|
| 132 |
+
the `etree`.
|
| 133 |
+
|
| 134 |
+
Note that both the `parent` and `etree` keywords are pointers
|
| 135 |
+
to instances of the objects which should be edited in place. Each
|
| 136 |
+
processor must make changes to the existing objects as there is no
|
| 137 |
+
mechanism to return new/different objects to replace them.
|
| 138 |
+
|
| 139 |
+
This means that this method should be adding `SubElements` or adding text
|
| 140 |
+
to the parent, and should remove (`pop`) or add (`insert`) items to
|
| 141 |
+
the list of blocks.
|
| 142 |
+
|
| 143 |
+
If `False` is returned, this will have the same effect as returning `False`
|
| 144 |
+
from the `test` method.
|
| 145 |
+
|
| 146 |
+
Keyword arguments:
|
| 147 |
+
parent: An `etree` element which is the parent of the current block.
|
| 148 |
+
blocks: A list of all remaining blocks of the document.
|
| 149 |
+
"""
|
| 150 |
+
pass # pragma: no cover
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class ListIndentProcessor(BlockProcessor):
|
| 154 |
+
""" Process children of list items.
|
| 155 |
+
|
| 156 |
+
Example
|
| 157 |
+
|
| 158 |
+
* a list item
|
| 159 |
+
process this part
|
| 160 |
+
|
| 161 |
+
or this part
|
| 162 |
+
|
| 163 |
+
"""
|
| 164 |
+
|
| 165 |
+
ITEM_TYPES = ['li']
|
| 166 |
+
""" List of tags used for list items. """
|
| 167 |
+
LIST_TYPES = ['ul', 'ol']
|
| 168 |
+
""" Types of lists this processor can operate on. """
|
| 169 |
+
|
| 170 |
+
def __init__(self, *args):
|
| 171 |
+
super().__init__(*args)
|
| 172 |
+
self.INDENT_RE = re.compile(r'^(([ ]{%s})+)' % self.tab_length)
|
| 173 |
+
|
| 174 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 175 |
+
return block.startswith(' '*self.tab_length) and \
|
| 176 |
+
not self.parser.state.isstate('detabbed') and \
|
| 177 |
+
(parent.tag in self.ITEM_TYPES or
|
| 178 |
+
(len(parent) and parent[-1] is not None and
|
| 179 |
+
(parent[-1].tag in self.LIST_TYPES)))
|
| 180 |
+
|
| 181 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 182 |
+
block = blocks.pop(0)
|
| 183 |
+
level, sibling = self.get_level(parent, block)
|
| 184 |
+
block = self.looseDetab(block, level)
|
| 185 |
+
|
| 186 |
+
self.parser.state.set('detabbed')
|
| 187 |
+
if parent.tag in self.ITEM_TYPES:
|
| 188 |
+
# It's possible that this parent has a `ul` or `ol` child list
|
| 189 |
+
# with a member. If that is the case, then that should be the
|
| 190 |
+
# parent. This is intended to catch the edge case of an indented
|
| 191 |
+
# list whose first member was parsed previous to this point
|
| 192 |
+
# see `OListProcessor`
|
| 193 |
+
if len(parent) and parent[-1].tag in self.LIST_TYPES:
|
| 194 |
+
self.parser.parseBlocks(parent[-1], [block])
|
| 195 |
+
else:
|
| 196 |
+
# The parent is already a `li`. Just parse the child block.
|
| 197 |
+
self.parser.parseBlocks(parent, [block])
|
| 198 |
+
elif sibling.tag in self.ITEM_TYPES:
|
| 199 |
+
# The sibling is a `li`. Use it as parent.
|
| 200 |
+
self.parser.parseBlocks(sibling, [block])
|
| 201 |
+
elif len(sibling) and sibling[-1].tag in self.ITEM_TYPES:
|
| 202 |
+
# The parent is a list (`ol` or `ul`) which has children.
|
| 203 |
+
# Assume the last child `li` is the parent of this block.
|
| 204 |
+
if sibling[-1].text:
|
| 205 |
+
# If the parent `li` has text, that text needs to be moved to a `p`
|
| 206 |
+
# The `p` must be 'inserted' at beginning of list in the event
|
| 207 |
+
# that other children already exist i.e.; a nested sub-list.
|
| 208 |
+
p = etree.Element('p')
|
| 209 |
+
p.text = sibling[-1].text
|
| 210 |
+
sibling[-1].text = ''
|
| 211 |
+
sibling[-1].insert(0, p)
|
| 212 |
+
self.parser.parseChunk(sibling[-1], block)
|
| 213 |
+
else:
|
| 214 |
+
self.create_item(sibling, block)
|
| 215 |
+
self.parser.state.reset()
|
| 216 |
+
|
| 217 |
+
def create_item(self, parent: etree.Element, block: str) -> None:
|
| 218 |
+
""" Create a new `li` and parse the block with it as the parent. """
|
| 219 |
+
li = etree.SubElement(parent, 'li')
|
| 220 |
+
self.parser.parseBlocks(li, [block])
|
| 221 |
+
|
| 222 |
+
def get_level(self, parent: etree.Element, block: str) -> tuple[int, etree.Element]:
|
| 223 |
+
""" Get level of indentation based on list level. """
|
| 224 |
+
# Get indent level
|
| 225 |
+
m = self.INDENT_RE.match(block)
|
| 226 |
+
if m:
|
| 227 |
+
indent_level = len(m.group(1))/self.tab_length
|
| 228 |
+
else:
|
| 229 |
+
indent_level = 0
|
| 230 |
+
if self.parser.state.isstate('list'):
|
| 231 |
+
# We're in a tight-list - so we already are at correct parent.
|
| 232 |
+
level = 1
|
| 233 |
+
else:
|
| 234 |
+
# We're in a loose-list - so we need to find parent.
|
| 235 |
+
level = 0
|
| 236 |
+
# Step through children of tree to find matching indent level.
|
| 237 |
+
while indent_level > level:
|
| 238 |
+
child = self.lastChild(parent)
|
| 239 |
+
if (child is not None and
|
| 240 |
+
(child.tag in self.LIST_TYPES or child.tag in self.ITEM_TYPES)):
|
| 241 |
+
if child.tag in self.LIST_TYPES:
|
| 242 |
+
level += 1
|
| 243 |
+
parent = child
|
| 244 |
+
else:
|
| 245 |
+
# No more child levels. If we're short of `indent_level`,
|
| 246 |
+
# we have a code block. So we stop here.
|
| 247 |
+
break
|
| 248 |
+
return level, parent
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
class CodeBlockProcessor(BlockProcessor):
|
| 252 |
+
""" Process code blocks. """
|
| 253 |
+
|
| 254 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 255 |
+
return block.startswith(' '*self.tab_length)
|
| 256 |
+
|
| 257 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 258 |
+
sibling = self.lastChild(parent)
|
| 259 |
+
block = blocks.pop(0)
|
| 260 |
+
theRest = ''
|
| 261 |
+
if (sibling is not None and sibling.tag == "pre" and
|
| 262 |
+
len(sibling) and sibling[0].tag == "code"):
|
| 263 |
+
# The previous block was a code block. As blank lines do not start
|
| 264 |
+
# new code blocks, append this block to the previous, adding back
|
| 265 |
+
# line breaks removed from the split into a list.
|
| 266 |
+
code = sibling[0]
|
| 267 |
+
block, theRest = self.detab(block)
|
| 268 |
+
code.text = util.AtomicString(
|
| 269 |
+
'{}\n{}\n'.format(code.text, util.code_escape(block.rstrip()))
|
| 270 |
+
)
|
| 271 |
+
else:
|
| 272 |
+
# This is a new code block. Create the elements and insert text.
|
| 273 |
+
pre = etree.SubElement(parent, 'pre')
|
| 274 |
+
code = etree.SubElement(pre, 'code')
|
| 275 |
+
block, theRest = self.detab(block)
|
| 276 |
+
code.text = util.AtomicString('%s\n' % util.code_escape(block.rstrip()))
|
| 277 |
+
if theRest:
|
| 278 |
+
# This block contained unindented line(s) after the first indented
|
| 279 |
+
# line. Insert these lines as the first block of the master blocks
|
| 280 |
+
# list for future processing.
|
| 281 |
+
blocks.insert(0, theRest)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
class BlockQuoteProcessor(BlockProcessor):
|
| 285 |
+
""" Process blockquotes. """
|
| 286 |
+
|
| 287 |
+
RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
|
| 288 |
+
|
| 289 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 290 |
+
return bool(self.RE.search(block)) and not util.nearing_recursion_limit()
|
| 291 |
+
|
| 292 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 293 |
+
block = blocks.pop(0)
|
| 294 |
+
m = self.RE.search(block)
|
| 295 |
+
if m:
|
| 296 |
+
before = block[:m.start()] # Lines before blockquote
|
| 297 |
+
# Pass lines before blockquote in recursively for parsing first.
|
| 298 |
+
self.parser.parseBlocks(parent, [before])
|
| 299 |
+
# Remove `> ` from beginning of each line.
|
| 300 |
+
block = '\n'.join(
|
| 301 |
+
[self.clean(line) for line in block[m.start():].split('\n')]
|
| 302 |
+
)
|
| 303 |
+
sibling = self.lastChild(parent)
|
| 304 |
+
if sibling is not None and sibling.tag == "blockquote":
|
| 305 |
+
# Previous block was a blockquote so set that as this blocks parent
|
| 306 |
+
quote = sibling
|
| 307 |
+
else:
|
| 308 |
+
# This is a new blockquote. Create a new parent element.
|
| 309 |
+
quote = etree.SubElement(parent, 'blockquote')
|
| 310 |
+
# Recursively parse block with blockquote as parent.
|
| 311 |
+
# change parser state so blockquotes embedded in lists use `p` tags
|
| 312 |
+
self.parser.state.set('blockquote')
|
| 313 |
+
self.parser.parseChunk(quote, block)
|
| 314 |
+
self.parser.state.reset()
|
| 315 |
+
|
| 316 |
+
def clean(self, line: str) -> str:
|
| 317 |
+
""" Remove `>` from beginning of a line. """
|
| 318 |
+
m = self.RE.match(line)
|
| 319 |
+
if line.strip() == ">":
|
| 320 |
+
return ""
|
| 321 |
+
elif m:
|
| 322 |
+
return m.group(2)
|
| 323 |
+
else:
|
| 324 |
+
return line
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
class OListProcessor(BlockProcessor):
|
| 328 |
+
""" Process ordered list blocks. """
|
| 329 |
+
|
| 330 |
+
TAG: str = 'ol'
|
| 331 |
+
""" The tag used for the the wrapping element. """
|
| 332 |
+
STARTSWITH: str = '1'
|
| 333 |
+
"""
|
| 334 |
+
The integer (as a string ) with which the list starts. For example, if a list is initialized as
|
| 335 |
+
`3. Item`, then the `ol` tag will be assigned an HTML attribute of `starts="3"`. Default: `"1"`.
|
| 336 |
+
"""
|
| 337 |
+
LAZY_OL: bool = True
|
| 338 |
+
""" Ignore `STARTSWITH` if `True`. """
|
| 339 |
+
SIBLING_TAGS: list[str] = ['ol', 'ul']
|
| 340 |
+
"""
|
| 341 |
+
Markdown does not require the type of a new list item match the previous list item type.
|
| 342 |
+
This is the list of types which can be mixed.
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
def __init__(self, parser: BlockParser):
|
| 346 |
+
super().__init__(parser)
|
| 347 |
+
# Detect an item (`1. item`). `group(1)` contains contents of item.
|
| 348 |
+
self.RE = re.compile(r'^[ ]{0,%d}\d+\.[ ]+(.*)' % (self.tab_length - 1))
|
| 349 |
+
# Detect items on secondary lines. they can be of either list type.
|
| 350 |
+
self.CHILD_RE = re.compile(r'^[ ]{0,%d}((\d+\.)|[*+-])[ ]+(.*)' %
|
| 351 |
+
(self.tab_length - 1))
|
| 352 |
+
# Detect indented (nested) items of either type
|
| 353 |
+
self.INDENT_RE = re.compile(r'^[ ]{%d,%d}((\d+\.)|[*+-])[ ]+.*' %
|
| 354 |
+
(self.tab_length, self.tab_length * 2 - 1))
|
| 355 |
+
|
| 356 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 357 |
+
return bool(self.RE.match(block))
|
| 358 |
+
|
| 359 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 360 |
+
# Check for multiple items in one block.
|
| 361 |
+
items = self.get_items(blocks.pop(0))
|
| 362 |
+
sibling = self.lastChild(parent)
|
| 363 |
+
|
| 364 |
+
if sibling is not None and sibling.tag in self.SIBLING_TAGS:
|
| 365 |
+
# Previous block was a list item, so set that as parent
|
| 366 |
+
lst = sibling
|
| 367 |
+
# make sure previous item is in a `p` - if the item has text,
|
| 368 |
+
# then it isn't in a `p`
|
| 369 |
+
if lst[-1].text:
|
| 370 |
+
# since it's possible there are other children for this
|
| 371 |
+
# sibling, we can't just `SubElement` the `p`, we need to
|
| 372 |
+
# insert it as the first item.
|
| 373 |
+
p = etree.Element('p')
|
| 374 |
+
p.text = lst[-1].text
|
| 375 |
+
lst[-1].text = ''
|
| 376 |
+
lst[-1].insert(0, p)
|
| 377 |
+
# if the last item has a tail, then the tail needs to be put in a `p`
|
| 378 |
+
# likely only when a header is not followed by a blank line
|
| 379 |
+
lch = self.lastChild(lst[-1])
|
| 380 |
+
if lch is not None and lch.tail:
|
| 381 |
+
p = etree.SubElement(lst[-1], 'p')
|
| 382 |
+
p.text = lch.tail.lstrip()
|
| 383 |
+
lch.tail = ''
|
| 384 |
+
|
| 385 |
+
# parse first block differently as it gets wrapped in a `p`.
|
| 386 |
+
li = etree.SubElement(lst, 'li')
|
| 387 |
+
self.parser.state.set('looselist')
|
| 388 |
+
firstitem = items.pop(0)
|
| 389 |
+
self.parser.parseBlocks(li, [firstitem])
|
| 390 |
+
self.parser.state.reset()
|
| 391 |
+
elif parent.tag in ['ol', 'ul']:
|
| 392 |
+
# this catches the edge case of a multi-item indented list whose
|
| 393 |
+
# first item is in a blank parent-list item:
|
| 394 |
+
# * * subitem1
|
| 395 |
+
# * subitem2
|
| 396 |
+
# see also `ListIndentProcessor`
|
| 397 |
+
lst = parent
|
| 398 |
+
else:
|
| 399 |
+
# This is a new list so create parent with appropriate tag.
|
| 400 |
+
lst = etree.SubElement(parent, self.TAG)
|
| 401 |
+
# Check if a custom start integer is set
|
| 402 |
+
if not self.LAZY_OL and self.STARTSWITH != '1':
|
| 403 |
+
lst.attrib['start'] = self.STARTSWITH
|
| 404 |
+
|
| 405 |
+
self.parser.state.set('list')
|
| 406 |
+
# Loop through items in block, recursively parsing each with the
|
| 407 |
+
# appropriate parent.
|
| 408 |
+
for item in items:
|
| 409 |
+
if item.startswith(' '*self.tab_length):
|
| 410 |
+
# Item is indented. Parse with last item as parent
|
| 411 |
+
self.parser.parseBlocks(lst[-1], [item])
|
| 412 |
+
else:
|
| 413 |
+
# New item. Create `li` and parse with it as parent
|
| 414 |
+
li = etree.SubElement(lst, 'li')
|
| 415 |
+
self.parser.parseBlocks(li, [item])
|
| 416 |
+
self.parser.state.reset()
|
| 417 |
+
|
| 418 |
+
def get_items(self, block: str) -> list[str]:
|
| 419 |
+
""" Break a block into list items. """
|
| 420 |
+
items = []
|
| 421 |
+
for line in block.split('\n'):
|
| 422 |
+
m = self.CHILD_RE.match(line)
|
| 423 |
+
if m:
|
| 424 |
+
# This is a new list item
|
| 425 |
+
# Check first item for the start index
|
| 426 |
+
if not items and self.TAG == 'ol':
|
| 427 |
+
# Detect the integer value of first list item
|
| 428 |
+
INTEGER_RE = re.compile(r'(\d+)')
|
| 429 |
+
self.STARTSWITH = INTEGER_RE.match(m.group(1)).group()
|
| 430 |
+
# Append to the list
|
| 431 |
+
items.append(m.group(3))
|
| 432 |
+
elif self.INDENT_RE.match(line):
|
| 433 |
+
# This is an indented (possibly nested) item.
|
| 434 |
+
if items[-1].startswith(' '*self.tab_length):
|
| 435 |
+
# Previous item was indented. Append to that item.
|
| 436 |
+
items[-1] = '{}\n{}'.format(items[-1], line)
|
| 437 |
+
else:
|
| 438 |
+
items.append(line)
|
| 439 |
+
else:
|
| 440 |
+
# This is another line of previous item. Append to that item.
|
| 441 |
+
items[-1] = '{}\n{}'.format(items[-1], line)
|
| 442 |
+
return items
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
class UListProcessor(OListProcessor):
|
| 446 |
+
""" Process unordered list blocks. """
|
| 447 |
+
|
| 448 |
+
TAG: str = 'ul'
|
| 449 |
+
""" The tag used for the the wrapping element. """
|
| 450 |
+
|
| 451 |
+
def __init__(self, parser: BlockParser):
|
| 452 |
+
super().__init__(parser)
|
| 453 |
+
# Detect an item (`1. item`). `group(1)` contains contents of item.
|
| 454 |
+
self.RE = re.compile(r'^[ ]{0,%d}[*+-][ ]+(.*)' % (self.tab_length - 1))
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
class HashHeaderProcessor(BlockProcessor):
|
| 458 |
+
""" Process Hash Headers. """
|
| 459 |
+
|
| 460 |
+
# Detect a header at start of any line in block
|
| 461 |
+
RE = re.compile(r'(?:^|\n)(?P<level>#{1,6})(?P<header>(?:\\.|[^\\])*?)#*(?:\n|$)')
|
| 462 |
+
|
| 463 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 464 |
+
return bool(self.RE.search(block))
|
| 465 |
+
|
| 466 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 467 |
+
block = blocks.pop(0)
|
| 468 |
+
m = self.RE.search(block)
|
| 469 |
+
if m:
|
| 470 |
+
before = block[:m.start()] # All lines before header
|
| 471 |
+
after = block[m.end():] # All lines after header
|
| 472 |
+
if before:
|
| 473 |
+
# As the header was not the first line of the block and the
|
| 474 |
+
# lines before the header must be parsed first,
|
| 475 |
+
# recursively parse this lines as a block.
|
| 476 |
+
self.parser.parseBlocks(parent, [before])
|
| 477 |
+
# Create header using named groups from RE
|
| 478 |
+
h = etree.SubElement(parent, 'h%d' % len(m.group('level')))
|
| 479 |
+
h.text = m.group('header').strip()
|
| 480 |
+
if after:
|
| 481 |
+
# Insert remaining lines as first block for future parsing.
|
| 482 |
+
if self.parser.state.isstate('looselist'):
|
| 483 |
+
# This is a weird edge case where a header is a child of a loose list
|
| 484 |
+
# and there is no blank line after the header. To ensure proper
|
| 485 |
+
# parsing, the line(s) after need to be detabbed. See #1443.
|
| 486 |
+
after = self.looseDetab(after)
|
| 487 |
+
blocks.insert(0, after)
|
| 488 |
+
else: # pragma: no cover
|
| 489 |
+
# This should never happen, but just in case...
|
| 490 |
+
logger.warn("We've got a problem header: %r" % block)
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
class SetextHeaderProcessor(BlockProcessor):
|
| 494 |
+
""" Process Setext-style Headers. """
|
| 495 |
+
|
| 496 |
+
# Detect Setext-style header. Must be first 2 lines of block.
|
| 497 |
+
RE = re.compile(r'^.*?\n[=-]+[ ]*(\n|$)', re.MULTILINE)
|
| 498 |
+
|
| 499 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 500 |
+
return bool(self.RE.match(block))
|
| 501 |
+
|
| 502 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 503 |
+
lines = blocks.pop(0).split('\n')
|
| 504 |
+
# Determine level. `=` is 1 and `-` is 2.
|
| 505 |
+
if lines[1].startswith('='):
|
| 506 |
+
level = 1
|
| 507 |
+
else:
|
| 508 |
+
level = 2
|
| 509 |
+
h = etree.SubElement(parent, 'h%d' % level)
|
| 510 |
+
h.text = lines[0].strip()
|
| 511 |
+
if len(lines) > 2:
|
| 512 |
+
# Block contains additional lines. Add to master blocks for later.
|
| 513 |
+
blocks.insert(0, '\n'.join(lines[2:]))
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
class HRProcessor(BlockProcessor):
|
| 517 |
+
""" Process Horizontal Rules. """
|
| 518 |
+
|
| 519 |
+
# Python's `re` module doesn't officially support atomic grouping. However you can fake it.
|
| 520 |
+
# See https://stackoverflow.com/a/13577411/866026
|
| 521 |
+
RE = r'^[ ]{0,3}(?=(?P<atomicgroup>(-+[ ]{0,2}){3,}|(_+[ ]{0,2}){3,}|(\*+[ ]{0,2}){3,}))(?P=atomicgroup)[ ]*$'
|
| 522 |
+
# Detect hr on any line of a block.
|
| 523 |
+
SEARCH_RE = re.compile(RE, re.MULTILINE)
|
| 524 |
+
|
| 525 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 526 |
+
m = self.SEARCH_RE.search(block)
|
| 527 |
+
if m:
|
| 528 |
+
# Save match object on class instance so we can use it later.
|
| 529 |
+
self.match = m
|
| 530 |
+
return True
|
| 531 |
+
return False
|
| 532 |
+
|
| 533 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 534 |
+
block = blocks.pop(0)
|
| 535 |
+
match = self.match
|
| 536 |
+
# Check for lines in block before `hr`.
|
| 537 |
+
prelines = block[:match.start()].rstrip('\n')
|
| 538 |
+
if prelines:
|
| 539 |
+
# Recursively parse lines before `hr` so they get parsed first.
|
| 540 |
+
self.parser.parseBlocks(parent, [prelines])
|
| 541 |
+
# create hr
|
| 542 |
+
etree.SubElement(parent, 'hr')
|
| 543 |
+
# check for lines in block after `hr`.
|
| 544 |
+
postlines = block[match.end():].lstrip('\n')
|
| 545 |
+
if postlines:
|
| 546 |
+
# Add lines after `hr` to master blocks for later parsing.
|
| 547 |
+
blocks.insert(0, postlines)
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
class EmptyBlockProcessor(BlockProcessor):
|
| 551 |
+
""" Process blocks that are empty or start with an empty line. """
|
| 552 |
+
|
| 553 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 554 |
+
return not block or block.startswith('\n')
|
| 555 |
+
|
| 556 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 557 |
+
block = blocks.pop(0)
|
| 558 |
+
filler = '\n\n'
|
| 559 |
+
if block:
|
| 560 |
+
# Starts with empty line
|
| 561 |
+
# Only replace a single line.
|
| 562 |
+
filler = '\n'
|
| 563 |
+
# Save the rest for later.
|
| 564 |
+
theRest = block[1:]
|
| 565 |
+
if theRest:
|
| 566 |
+
# Add remaining lines to master blocks for later.
|
| 567 |
+
blocks.insert(0, theRest)
|
| 568 |
+
sibling = self.lastChild(parent)
|
| 569 |
+
if (sibling is not None and sibling.tag == 'pre' and
|
| 570 |
+
len(sibling) and sibling[0].tag == 'code'):
|
| 571 |
+
# Last block is a code block. Append to preserve whitespace.
|
| 572 |
+
sibling[0].text = util.AtomicString(
|
| 573 |
+
'{}{}'.format(sibling[0].text, filler)
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
class ReferenceProcessor(BlockProcessor):
|
| 578 |
+
""" Process link references. """
|
| 579 |
+
RE = re.compile(
|
| 580 |
+
r'^[ ]{0,3}\[([^\[\]]*)\]:[ ]*\n?[ ]*([^\s]+)[ ]*(?:\n[ ]*)?((["\'])(.*)\4[ ]*|\((.*)\)[ ]*)?$', re.MULTILINE
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 584 |
+
return True
|
| 585 |
+
|
| 586 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> bool:
|
| 587 |
+
block = blocks.pop(0)
|
| 588 |
+
m = self.RE.search(block)
|
| 589 |
+
if m:
|
| 590 |
+
id = m.group(1).strip().lower()
|
| 591 |
+
link = m.group(2).lstrip('<').rstrip('>')
|
| 592 |
+
title = m.group(5) or m.group(6)
|
| 593 |
+
self.parser.md.references[id] = (link, title)
|
| 594 |
+
if block[m.end():].strip():
|
| 595 |
+
# Add any content after match back to blocks as separate block
|
| 596 |
+
blocks.insert(0, block[m.end():].lstrip('\n'))
|
| 597 |
+
if block[:m.start()].strip():
|
| 598 |
+
# Add any content before match back to blocks as separate block
|
| 599 |
+
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
| 600 |
+
return True
|
| 601 |
+
# No match. Restore block.
|
| 602 |
+
blocks.insert(0, block)
|
| 603 |
+
return False
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
class ParagraphProcessor(BlockProcessor):
|
| 607 |
+
""" Process Paragraph blocks. """
|
| 608 |
+
|
| 609 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 610 |
+
return True
|
| 611 |
+
|
| 612 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 613 |
+
block = blocks.pop(0)
|
| 614 |
+
if block.strip():
|
| 615 |
+
# Not a blank block. Add to parent, otherwise throw it away.
|
| 616 |
+
if self.parser.state.isstate('list'):
|
| 617 |
+
# The parent is a tight-list.
|
| 618 |
+
#
|
| 619 |
+
# Check for any children. This will likely only happen in a
|
| 620 |
+
# tight-list when a header isn't followed by a blank line.
|
| 621 |
+
# For example:
|
| 622 |
+
#
|
| 623 |
+
# * # Header
|
| 624 |
+
# Line 2 of list item - not part of header.
|
| 625 |
+
sibling = self.lastChild(parent)
|
| 626 |
+
if sibling is not None:
|
| 627 |
+
# Insert after sibling.
|
| 628 |
+
if sibling.tail:
|
| 629 |
+
sibling.tail = '{}\n{}'.format(sibling.tail, block)
|
| 630 |
+
else:
|
| 631 |
+
sibling.tail = '\n%s' % block
|
| 632 |
+
else:
|
| 633 |
+
# Append to parent.text
|
| 634 |
+
if parent.text:
|
| 635 |
+
parent.text = '{}\n{}'.format(parent.text, block)
|
| 636 |
+
else:
|
| 637 |
+
parent.text = block.lstrip()
|
| 638 |
+
else:
|
| 639 |
+
# Create a regular paragraph
|
| 640 |
+
p = etree.SubElement(parent, 'p')
|
| 641 |
+
p.text = block.lstrip()
|
lib/python3.12/site-packages/markdown/core.py
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import codecs
|
| 23 |
+
import sys
|
| 24 |
+
import logging
|
| 25 |
+
import importlib
|
| 26 |
+
from typing import TYPE_CHECKING, Any, BinaryIO, Callable, ClassVar, Mapping, Sequence
|
| 27 |
+
from . import util
|
| 28 |
+
from .preprocessors import build_preprocessors
|
| 29 |
+
from .blockprocessors import build_block_parser
|
| 30 |
+
from .treeprocessors import build_treeprocessors
|
| 31 |
+
from .inlinepatterns import build_inlinepatterns
|
| 32 |
+
from .postprocessors import build_postprocessors
|
| 33 |
+
from .extensions import Extension
|
| 34 |
+
from .serializers import to_html_string, to_xhtml_string
|
| 35 |
+
from .util import BLOCK_LEVEL_ELEMENTS
|
| 36 |
+
|
| 37 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 38 |
+
from xml.etree.ElementTree import Element
|
| 39 |
+
|
| 40 |
+
__all__ = ['Markdown', 'markdown', 'markdownFromFile']
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
logger = logging.getLogger('MARKDOWN')
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class Markdown:
|
| 47 |
+
"""
|
| 48 |
+
A parser which converts Markdown to HTML.
|
| 49 |
+
|
| 50 |
+
Attributes:
|
| 51 |
+
Markdown.tab_length (int): The number of spaces which correspond to a single tab. Default: `4`.
|
| 52 |
+
Markdown.ESCAPED_CHARS (list[str]): List of characters which get the backslash escape treatment.
|
| 53 |
+
Markdown.block_level_elements (list[str]): List of HTML tags which get treated as block-level elements.
|
| 54 |
+
See [`markdown.util.BLOCK_LEVEL_ELEMENTS`][] for the full list of elements.
|
| 55 |
+
Markdown.registeredExtensions (list[Extension]): List of extensions which have called
|
| 56 |
+
[`registerExtension`][markdown.Markdown.registerExtension] during setup.
|
| 57 |
+
Markdown.doc_tag (str): Element used to wrap document. Default: `div`.
|
| 58 |
+
Markdown.stripTopLevelTags (bool): Indicates whether the `doc_tag` should be removed. Default: 'True'.
|
| 59 |
+
Markdown.references (dict[str, tuple[str, str]]): A mapping of link references found in a parsed document
|
| 60 |
+
where the key is the reference name and the value is a tuple of the URL and title.
|
| 61 |
+
Markdown.htmlStash (util.HtmlStash): The instance of the `HtmlStash` used by an instance of this class.
|
| 62 |
+
Markdown.output_formats (dict[str, Callable[xml.etree.ElementTree.Element]]): A mapping of known output
|
| 63 |
+
formats by name and their respective serializers. Each serializer must be a callable which accepts an
|
| 64 |
+
[`Element`][xml.etree.ElementTree.Element] and returns a `str`.
|
| 65 |
+
Markdown.output_format (str): The output format set by
|
| 66 |
+
[`set_output_format`][markdown.Markdown.set_output_format].
|
| 67 |
+
Markdown.serializer (Callable[xml.etree.ElementTree.Element]): The serializer set by
|
| 68 |
+
[`set_output_format`][markdown.Markdown.set_output_format].
|
| 69 |
+
Markdown.preprocessors (util.Registry): A collection of [`preprocessors`][markdown.preprocessors].
|
| 70 |
+
Markdown.parser (blockparser.BlockParser): A collection of [`blockprocessors`][markdown.blockprocessors].
|
| 71 |
+
Markdown.inlinePatterns (util.Registry): A collection of [`inlinepatterns`][markdown.inlinepatterns].
|
| 72 |
+
Markdown.treeprocessors (util.Registry): A collection of [`treeprocessors`][markdown.treeprocessors].
|
| 73 |
+
Markdown.postprocessors (util.Registry): A collection of [`postprocessors`][markdown.postprocessors].
|
| 74 |
+
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
doc_tag = "div" # Element used to wrap document - later removed
|
| 78 |
+
|
| 79 |
+
output_formats: ClassVar[dict[str, Callable[[Element], str]]] = {
|
| 80 |
+
'html': to_html_string,
|
| 81 |
+
'xhtml': to_xhtml_string,
|
| 82 |
+
}
|
| 83 |
+
"""
|
| 84 |
+
A mapping of known output formats by name and their respective serializers. Each serializer must be a
|
| 85 |
+
callable which accepts an [`Element`][xml.etree.ElementTree.Element] and returns a `str`.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(self, **kwargs):
|
| 89 |
+
"""
|
| 90 |
+
Creates a new Markdown instance.
|
| 91 |
+
|
| 92 |
+
Keyword Arguments:
|
| 93 |
+
extensions (list[Extension | str]): A list of extensions.
|
| 94 |
+
|
| 95 |
+
If an item is an instance of a subclass of [`markdown.extensions.Extension`][],
|
| 96 |
+
the instance will be used as-is. If an item is of type `str`, it is passed
|
| 97 |
+
to [`build_extension`][markdown.Markdown.build_extension] with its corresponding
|
| 98 |
+
`extension_configs` and the returned instance of [`markdown.extensions.Extension`][]
|
| 99 |
+
is used.
|
| 100 |
+
extension_configs (dict[str, dict[str, Any]]): Configuration settings for extensions.
|
| 101 |
+
output_format (str): Format of output. Supported formats are:
|
| 102 |
+
|
| 103 |
+
* `xhtml`: Outputs XHTML style tags. Default.
|
| 104 |
+
* `html`: Outputs HTML style tags.
|
| 105 |
+
tab_length (int): Length of tabs in the source. Default: `4`
|
| 106 |
+
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
self.tab_length: int = kwargs.get('tab_length', 4)
|
| 110 |
+
|
| 111 |
+
self.ESCAPED_CHARS: list[str] = [
|
| 112 |
+
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!'
|
| 113 |
+
]
|
| 114 |
+
""" List of characters which get the backslash escape treatment. """
|
| 115 |
+
|
| 116 |
+
self.block_level_elements: list[str] = BLOCK_LEVEL_ELEMENTS.copy()
|
| 117 |
+
|
| 118 |
+
self.registeredExtensions: list[Extension] = []
|
| 119 |
+
self.docType = "" # TODO: Maybe delete this. It does not appear to be used anymore.
|
| 120 |
+
self.stripTopLevelTags: bool = True
|
| 121 |
+
|
| 122 |
+
self.build_parser()
|
| 123 |
+
|
| 124 |
+
self.references: dict[str, tuple[str, str]] = {}
|
| 125 |
+
self.htmlStash: util.HtmlStash = util.HtmlStash()
|
| 126 |
+
self.registerExtensions(extensions=kwargs.get('extensions', []),
|
| 127 |
+
configs=kwargs.get('extension_configs', {}))
|
| 128 |
+
self.set_output_format(kwargs.get('output_format', 'xhtml'))
|
| 129 |
+
self.reset()
|
| 130 |
+
|
| 131 |
+
def build_parser(self) -> Markdown:
|
| 132 |
+
"""
|
| 133 |
+
Build the parser from the various parts.
|
| 134 |
+
|
| 135 |
+
Assigns a value to each of the following attributes on the class instance:
|
| 136 |
+
|
| 137 |
+
* **`Markdown.preprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
|
| 138 |
+
[`preprocessors`][markdown.preprocessors].
|
| 139 |
+
* **`Markdown.parser`** ([`BlockParser`][markdown.blockparser.BlockParser]) -- A collection of
|
| 140 |
+
[`blockprocessors`][markdown.blockprocessors].
|
| 141 |
+
* **`Markdown.inlinePatterns`** ([`Registry`][markdown.util.Registry]) -- A collection of
|
| 142 |
+
[`inlinepatterns`][markdown.inlinepatterns].
|
| 143 |
+
* **`Markdown.treeprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
|
| 144 |
+
[`treeprocessors`][markdown.treeprocessors].
|
| 145 |
+
* **`Markdown.postprocessors`** ([`Registry`][markdown.util.Registry]) -- A collection of
|
| 146 |
+
[`postprocessors`][markdown.postprocessors].
|
| 147 |
+
|
| 148 |
+
This method could be redefined in a subclass to build a custom parser which is made up of a different
|
| 149 |
+
combination of processors and patterns.
|
| 150 |
+
|
| 151 |
+
"""
|
| 152 |
+
self.preprocessors = build_preprocessors(self)
|
| 153 |
+
self.parser = build_block_parser(self)
|
| 154 |
+
self.inlinePatterns = build_inlinepatterns(self)
|
| 155 |
+
self.treeprocessors = build_treeprocessors(self)
|
| 156 |
+
self.postprocessors = build_postprocessors(self)
|
| 157 |
+
return self
|
| 158 |
+
|
| 159 |
+
def registerExtensions(
|
| 160 |
+
self,
|
| 161 |
+
extensions: Sequence[Extension | str],
|
| 162 |
+
configs: Mapping[str, dict[str, Any]]
|
| 163 |
+
) -> Markdown:
|
| 164 |
+
"""
|
| 165 |
+
Load a list of extensions into an instance of the `Markdown` class.
|
| 166 |
+
|
| 167 |
+
Arguments:
|
| 168 |
+
extensions (list[Extension | str]): A list of extensions.
|
| 169 |
+
|
| 170 |
+
If an item is an instance of a subclass of [`markdown.extensions.Extension`][],
|
| 171 |
+
the instance will be used as-is. If an item is of type `str`, it is passed
|
| 172 |
+
to [`build_extension`][markdown.Markdown.build_extension] with its corresponding `configs` and the
|
| 173 |
+
returned instance of [`markdown.extensions.Extension`][] is used.
|
| 174 |
+
configs (dict[str, dict[str, Any]]): Configuration settings for extensions.
|
| 175 |
+
|
| 176 |
+
"""
|
| 177 |
+
for ext in extensions:
|
| 178 |
+
if isinstance(ext, str):
|
| 179 |
+
ext = self.build_extension(ext, configs.get(ext, {}))
|
| 180 |
+
if isinstance(ext, Extension):
|
| 181 |
+
ext.extendMarkdown(self)
|
| 182 |
+
logger.debug(
|
| 183 |
+
'Successfully loaded extension "%s.%s".'
|
| 184 |
+
% (ext.__class__.__module__, ext.__class__.__name__)
|
| 185 |
+
)
|
| 186 |
+
elif ext is not None:
|
| 187 |
+
raise TypeError(
|
| 188 |
+
'Extension "{}.{}" must be of type: "{}.{}"'.format(
|
| 189 |
+
ext.__class__.__module__, ext.__class__.__name__,
|
| 190 |
+
Extension.__module__, Extension.__name__
|
| 191 |
+
)
|
| 192 |
+
)
|
| 193 |
+
return self
|
| 194 |
+
|
| 195 |
+
def build_extension(self, ext_name: str, configs: Mapping[str, Any]) -> Extension:
|
| 196 |
+
"""
|
| 197 |
+
Build extension from a string name, then return an instance using the given `configs`.
|
| 198 |
+
|
| 199 |
+
Arguments:
|
| 200 |
+
ext_name: Name of extension as a string.
|
| 201 |
+
configs: Configuration settings for extension.
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
An instance of the extension with the given configuration settings.
|
| 205 |
+
|
| 206 |
+
First attempt to load an entry point. The string name must be registered as an entry point in the
|
| 207 |
+
`markdown.extensions` group which points to a subclass of the [`markdown.extensions.Extension`][] class.
|
| 208 |
+
If multiple distributions have registered the same name, the first one found is returned.
|
| 209 |
+
|
| 210 |
+
If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
|
| 211 |
+
return an instance. If no class is specified, import the module and call a `makeExtension` function and return
|
| 212 |
+
the [`markdown.extensions.Extension`][] instance returned by that function.
|
| 213 |
+
"""
|
| 214 |
+
configs = dict(configs)
|
| 215 |
+
|
| 216 |
+
entry_points = [ep for ep in util.get_installed_extensions() if ep.name == ext_name]
|
| 217 |
+
if entry_points:
|
| 218 |
+
ext = entry_points[0].load()
|
| 219 |
+
return ext(**configs)
|
| 220 |
+
|
| 221 |
+
# Get class name (if provided): `path.to.module:ClassName`
|
| 222 |
+
ext_name, class_name = ext_name.split(':', 1) if ':' in ext_name else (ext_name, '')
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
module = importlib.import_module(ext_name)
|
| 226 |
+
logger.debug(
|
| 227 |
+
'Successfully imported extension module "%s".' % ext_name
|
| 228 |
+
)
|
| 229 |
+
except ImportError as e:
|
| 230 |
+
message = 'Failed loading extension "%s".' % ext_name
|
| 231 |
+
e.args = (message,) + e.args[1:]
|
| 232 |
+
raise
|
| 233 |
+
|
| 234 |
+
if class_name:
|
| 235 |
+
# Load given class name from module.
|
| 236 |
+
return getattr(module, class_name)(**configs)
|
| 237 |
+
else:
|
| 238 |
+
# Expect `makeExtension()` function to return a class.
|
| 239 |
+
try:
|
| 240 |
+
return module.makeExtension(**configs)
|
| 241 |
+
except AttributeError as e:
|
| 242 |
+
message = e.args[0]
|
| 243 |
+
message = "Failed to initiate extension " \
|
| 244 |
+
"'%s': %s" % (ext_name, message)
|
| 245 |
+
e.args = (message,) + e.args[1:]
|
| 246 |
+
raise
|
| 247 |
+
|
| 248 |
+
def registerExtension(self, extension: Extension) -> Markdown:
|
| 249 |
+
"""
|
| 250 |
+
Register an extension as having a resettable state.
|
| 251 |
+
|
| 252 |
+
Arguments:
|
| 253 |
+
extension: An instance of the extension to register.
|
| 254 |
+
|
| 255 |
+
This should get called once by an extension during setup. A "registered" extension's
|
| 256 |
+
`reset` method is called by [`Markdown.reset()`][markdown.Markdown.reset]. Not all extensions have or need a
|
| 257 |
+
resettable state, and so it should not be assumed that all extensions are "registered."
|
| 258 |
+
|
| 259 |
+
"""
|
| 260 |
+
self.registeredExtensions.append(extension)
|
| 261 |
+
return self
|
| 262 |
+
|
| 263 |
+
def reset(self) -> Markdown:
|
| 264 |
+
"""
|
| 265 |
+
Resets all state variables to prepare the parser instance for new input.
|
| 266 |
+
|
| 267 |
+
Called once upon creation of a class instance. Should be called manually between calls
|
| 268 |
+
to [`Markdown.convert`][markdown.Markdown.convert].
|
| 269 |
+
"""
|
| 270 |
+
self.htmlStash.reset()
|
| 271 |
+
self.references.clear()
|
| 272 |
+
|
| 273 |
+
for extension in self.registeredExtensions:
|
| 274 |
+
if hasattr(extension, 'reset'):
|
| 275 |
+
extension.reset()
|
| 276 |
+
|
| 277 |
+
return self
|
| 278 |
+
|
| 279 |
+
def set_output_format(self, format: str) -> Markdown:
|
| 280 |
+
"""
|
| 281 |
+
Set the output format for the class instance.
|
| 282 |
+
|
| 283 |
+
Arguments:
|
| 284 |
+
format: Must be a known value in `Markdown.output_formats`.
|
| 285 |
+
|
| 286 |
+
"""
|
| 287 |
+
self.output_format = format.lower().rstrip('145') # ignore number
|
| 288 |
+
try:
|
| 289 |
+
self.serializer = self.output_formats[self.output_format]
|
| 290 |
+
except KeyError as e:
|
| 291 |
+
valid_formats = list(self.output_formats.keys())
|
| 292 |
+
valid_formats.sort()
|
| 293 |
+
message = 'Invalid Output Format: "%s". Use one of %s.' \
|
| 294 |
+
% (self.output_format,
|
| 295 |
+
'"' + '", "'.join(valid_formats) + '"')
|
| 296 |
+
e.args = (message,) + e.args[1:]
|
| 297 |
+
raise
|
| 298 |
+
return self
|
| 299 |
+
|
| 300 |
+
# Note: the `tag` argument is type annotated `Any` as ElementTree uses many various objects as tags.
|
| 301 |
+
# As there is no standardization in ElementTree, the type of a given tag is unpredictable.
|
| 302 |
+
def is_block_level(self, tag: Any) -> bool:
|
| 303 |
+
"""
|
| 304 |
+
Check if the given `tag` is a block level HTML tag.
|
| 305 |
+
|
| 306 |
+
Returns `True` for any string listed in `Markdown.block_level_elements`. A `tag` which is
|
| 307 |
+
not a string always returns `False`.
|
| 308 |
+
|
| 309 |
+
"""
|
| 310 |
+
if isinstance(tag, str):
|
| 311 |
+
return tag.lower().rstrip('/') in self.block_level_elements
|
| 312 |
+
# Some ElementTree tags are not strings, so return False.
|
| 313 |
+
return False
|
| 314 |
+
|
| 315 |
+
def convert(self, source: str) -> str:
|
| 316 |
+
"""
|
| 317 |
+
Convert a Markdown string to a string in the specified output format.
|
| 318 |
+
|
| 319 |
+
Arguments:
|
| 320 |
+
source: Markdown formatted text as Unicode or ASCII string.
|
| 321 |
+
|
| 322 |
+
Returns:
|
| 323 |
+
A string in the specified output format.
|
| 324 |
+
|
| 325 |
+
Markdown parsing takes place in five steps:
|
| 326 |
+
|
| 327 |
+
1. A bunch of [`preprocessors`][markdown.preprocessors] munge the input text.
|
| 328 |
+
2. A [`BlockParser`][markdown.blockparser.BlockParser] parses the high-level structural elements of the
|
| 329 |
+
pre-processed text into an [`ElementTree`][xml.etree.ElementTree.ElementTree] object.
|
| 330 |
+
3. A bunch of [`treeprocessors`][markdown.treeprocessors] are run against the
|
| 331 |
+
[`ElementTree`][xml.etree.ElementTree.ElementTree] object. One such `treeprocessor`
|
| 332 |
+
([`markdown.treeprocessors.InlineProcessor`][]) runs [`inlinepatterns`][markdown.inlinepatterns]
|
| 333 |
+
against the [`ElementTree`][xml.etree.ElementTree.ElementTree] object, parsing inline markup.
|
| 334 |
+
4. Some [`postprocessors`][markdown.postprocessors] are run against the text after the
|
| 335 |
+
[`ElementTree`][xml.etree.ElementTree.ElementTree] object has been serialized into text.
|
| 336 |
+
5. The output is returned as a string.
|
| 337 |
+
|
| 338 |
+
"""
|
| 339 |
+
|
| 340 |
+
# Fix up the source text
|
| 341 |
+
if not source.strip():
|
| 342 |
+
return '' # a blank Unicode string
|
| 343 |
+
|
| 344 |
+
try:
|
| 345 |
+
source = str(source)
|
| 346 |
+
except UnicodeDecodeError as e: # pragma: no cover
|
| 347 |
+
# Customize error message while maintaining original traceback
|
| 348 |
+
e.reason += '. -- Note: Markdown only accepts Unicode input!'
|
| 349 |
+
raise
|
| 350 |
+
|
| 351 |
+
# Split into lines and run the line preprocessors.
|
| 352 |
+
self.lines = source.split("\n")
|
| 353 |
+
for prep in self.preprocessors:
|
| 354 |
+
self.lines = prep.run(self.lines)
|
| 355 |
+
|
| 356 |
+
# Parse the high-level elements.
|
| 357 |
+
root = self.parser.parseDocument(self.lines).getroot()
|
| 358 |
+
|
| 359 |
+
# Run the tree-processors
|
| 360 |
+
for treeprocessor in self.treeprocessors:
|
| 361 |
+
newRoot = treeprocessor.run(root)
|
| 362 |
+
if newRoot is not None:
|
| 363 |
+
root = newRoot
|
| 364 |
+
|
| 365 |
+
# Serialize _properly_. Strip top-level tags.
|
| 366 |
+
output = self.serializer(root)
|
| 367 |
+
if self.stripTopLevelTags:
|
| 368 |
+
try:
|
| 369 |
+
start = output.index(
|
| 370 |
+
'<%s>' % self.doc_tag) + len(self.doc_tag) + 2
|
| 371 |
+
end = output.rindex('</%s>' % self.doc_tag)
|
| 372 |
+
output = output[start:end].strip()
|
| 373 |
+
except ValueError as e: # pragma: no cover
|
| 374 |
+
if output.strip().endswith('<%s />' % self.doc_tag):
|
| 375 |
+
# We have an empty document
|
| 376 |
+
output = ''
|
| 377 |
+
else:
|
| 378 |
+
# We have a serious problem
|
| 379 |
+
raise ValueError('Markdown failed to strip top-level '
|
| 380 |
+
'tags. Document=%r' % output.strip()) from e
|
| 381 |
+
|
| 382 |
+
# Run the text post-processors
|
| 383 |
+
for pp in self.postprocessors:
|
| 384 |
+
output = pp.run(output)
|
| 385 |
+
|
| 386 |
+
return output.strip()
|
| 387 |
+
|
| 388 |
+
def convertFile(
|
| 389 |
+
self,
|
| 390 |
+
input: str | BinaryIO | None = None,
|
| 391 |
+
output: str | BinaryIO | None = None,
|
| 392 |
+
encoding: str | None = None,
|
| 393 |
+
) -> Markdown:
|
| 394 |
+
"""
|
| 395 |
+
Converts a Markdown file and returns the HTML as a Unicode string.
|
| 396 |
+
|
| 397 |
+
Decodes the file using the provided encoding (defaults to `utf-8`),
|
| 398 |
+
passes the file content to markdown, and outputs the HTML to either
|
| 399 |
+
the provided stream or the file with provided name, using the same
|
| 400 |
+
encoding as the source file. The
|
| 401 |
+
[`xmlcharrefreplace`](https://docs.python.org/3/library/codecs.html#error-handlers)
|
| 402 |
+
error handler is used when encoding the output.
|
| 403 |
+
|
| 404 |
+
**Note:** This is the only place that decoding and encoding of Unicode
|
| 405 |
+
takes place in Python-Markdown. (All other code is Unicode-in /
|
| 406 |
+
Unicode-out.)
|
| 407 |
+
|
| 408 |
+
Arguments:
|
| 409 |
+
input: File object or path. Reads from `stdin` if `None`.
|
| 410 |
+
output: File object or path. Writes to `stdout` if `None`.
|
| 411 |
+
encoding: Encoding of input and output files. Defaults to `utf-8`.
|
| 412 |
+
|
| 413 |
+
"""
|
| 414 |
+
|
| 415 |
+
encoding = encoding or "utf-8"
|
| 416 |
+
|
| 417 |
+
# Read the source
|
| 418 |
+
if input:
|
| 419 |
+
if isinstance(input, str):
|
| 420 |
+
input_file = open(input, mode="r", encoding=encoding)
|
| 421 |
+
else:
|
| 422 |
+
input_file = codecs.getreader(encoding)(input)
|
| 423 |
+
text = input_file.read()
|
| 424 |
+
input_file.close()
|
| 425 |
+
else:
|
| 426 |
+
text = sys.stdin.read()
|
| 427 |
+
|
| 428 |
+
text = text.lstrip('\ufeff') # remove the byte-order mark
|
| 429 |
+
|
| 430 |
+
# Convert
|
| 431 |
+
html = self.convert(text)
|
| 432 |
+
|
| 433 |
+
# Write to file or stdout
|
| 434 |
+
if output:
|
| 435 |
+
if isinstance(output, str):
|
| 436 |
+
output_file = codecs.open(output, "w",
|
| 437 |
+
encoding=encoding,
|
| 438 |
+
errors="xmlcharrefreplace")
|
| 439 |
+
output_file.write(html)
|
| 440 |
+
output_file.close()
|
| 441 |
+
else:
|
| 442 |
+
writer = codecs.getwriter(encoding)
|
| 443 |
+
output_file = writer(output, errors="xmlcharrefreplace")
|
| 444 |
+
output_file.write(html)
|
| 445 |
+
# Don't close here. User may want to write more.
|
| 446 |
+
else:
|
| 447 |
+
# Encode manually and write bytes to stdout.
|
| 448 |
+
html = html.encode(encoding, "xmlcharrefreplace")
|
| 449 |
+
sys.stdout.buffer.write(html)
|
| 450 |
+
|
| 451 |
+
return self
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
"""
|
| 455 |
+
EXPORTED FUNCTIONS
|
| 456 |
+
=============================================================================
|
| 457 |
+
|
| 458 |
+
Those are the two functions we really mean to export: `markdown()` and
|
| 459 |
+
`markdownFromFile()`.
|
| 460 |
+
"""
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def markdown(text: str, **kwargs: Any) -> str:
|
| 464 |
+
"""
|
| 465 |
+
Convert a markdown string to HTML and return HTML as a Unicode string.
|
| 466 |
+
|
| 467 |
+
This is a shortcut function for [`Markdown`][markdown.Markdown] class to cover the most
|
| 468 |
+
basic use case. It initializes an instance of [`Markdown`][markdown.Markdown], loads the
|
| 469 |
+
necessary extensions and runs the parser on the given text.
|
| 470 |
+
|
| 471 |
+
Arguments:
|
| 472 |
+
text: Markdown formatted text as Unicode or ASCII string.
|
| 473 |
+
|
| 474 |
+
Keyword arguments:
|
| 475 |
+
**kwargs: Any arguments accepted by the Markdown class.
|
| 476 |
+
|
| 477 |
+
Returns:
|
| 478 |
+
A string in the specified output format.
|
| 479 |
+
|
| 480 |
+
"""
|
| 481 |
+
md = Markdown(**kwargs)
|
| 482 |
+
return md.convert(text)
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def markdownFromFile(**kwargs: Any):
|
| 486 |
+
"""
|
| 487 |
+
Read Markdown text from a file and write output to a file or a stream.
|
| 488 |
+
|
| 489 |
+
This is a shortcut function which initializes an instance of [`Markdown`][markdown.Markdown],
|
| 490 |
+
and calls the [`convertFile`][markdown.Markdown.convertFile] method rather than
|
| 491 |
+
[`convert`][markdown.Markdown.convert].
|
| 492 |
+
|
| 493 |
+
Keyword arguments:
|
| 494 |
+
input (str | BinaryIO): A file name or readable object.
|
| 495 |
+
output (str | BinaryIO): A file name or writable object.
|
| 496 |
+
encoding (str): Encoding of input and output.
|
| 497 |
+
**kwargs: Any arguments accepted by the `Markdown` class.
|
| 498 |
+
|
| 499 |
+
"""
|
| 500 |
+
md = Markdown(**kwargs)
|
| 501 |
+
md.convertFile(kwargs.get('input', None),
|
| 502 |
+
kwargs.get('output', None),
|
| 503 |
+
kwargs.get('encoding', None))
|
lib/python3.12/site-packages/markdown/extensions/__init__.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python Markdown
|
| 2 |
+
|
| 3 |
+
# A Python implementation of John Gruber's Markdown.
|
| 4 |
+
|
| 5 |
+
# Documentation: https://python-markdown.github.io/
|
| 6 |
+
# GitHub: https://github.com/Python-Markdown/markdown/
|
| 7 |
+
# PyPI: https://pypi.org/project/Markdown/
|
| 8 |
+
|
| 9 |
+
# Started by Manfred Stienstra (http://www.dwerg.net/).
|
| 10 |
+
# Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org).
|
| 11 |
+
# Currently maintained by Waylan Limberg (https://github.com/waylan),
|
| 12 |
+
# Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser).
|
| 13 |
+
|
| 14 |
+
# Copyright 2007-2023 The Python Markdown Project (v. 1.7 and later)
|
| 15 |
+
# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
| 16 |
+
# Copyright 2004 Manfred Stienstra (the original version)
|
| 17 |
+
|
| 18 |
+
# License: BSD (see LICENSE.md for details).
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
Markdown accepts an [`Extension`][markdown.extensions.Extension] instance for each extension. Therefore, each extension
|
| 22 |
+
must to define a class that extends [`Extension`][markdown.extensions.Extension] and over-rides the
|
| 23 |
+
[`extendMarkdown`][markdown.extensions.Extension.extendMarkdown] method. Within this class one can manage configuration
|
| 24 |
+
options for their extension and attach the various processors and patterns which make up an extension to the
|
| 25 |
+
[`Markdown`][markdown.Markdown] instance.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
from typing import TYPE_CHECKING, Any, Iterable, Mapping
|
| 31 |
+
from ..util import parseBoolValue
|
| 32 |
+
|
| 33 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 34 |
+
from markdown import Markdown
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class Extension:
|
| 38 |
+
""" Base class for extensions to subclass. """
|
| 39 |
+
|
| 40 |
+
config: Mapping[str, list] = {}
|
| 41 |
+
"""
|
| 42 |
+
Default configuration for an extension.
|
| 43 |
+
|
| 44 |
+
This attribute is to be defined in a subclass and must be of the following format:
|
| 45 |
+
|
| 46 |
+
``` python
|
| 47 |
+
config = {
|
| 48 |
+
'key': ['value', 'description']
|
| 49 |
+
}
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Note that [`setConfig`][markdown.extensions.Extension.setConfig] will raise a [`KeyError`][]
|
| 53 |
+
if a default is not set for each option.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __init__(self, **kwargs):
|
| 57 |
+
""" Initiate Extension and set up configs. """
|
| 58 |
+
self.setConfigs(kwargs)
|
| 59 |
+
|
| 60 |
+
def getConfig(self, key: str, default: Any = '') -> Any:
|
| 61 |
+
"""
|
| 62 |
+
Return a single configuration option value.
|
| 63 |
+
|
| 64 |
+
Arguments:
|
| 65 |
+
key: The configuration option name.
|
| 66 |
+
default: Default value to return if key is not set.
|
| 67 |
+
|
| 68 |
+
Returns:
|
| 69 |
+
Value of stored configuration option.
|
| 70 |
+
"""
|
| 71 |
+
if key in self.config:
|
| 72 |
+
return self.config[key][0]
|
| 73 |
+
else:
|
| 74 |
+
return default
|
| 75 |
+
|
| 76 |
+
def getConfigs(self) -> dict[str, Any]:
|
| 77 |
+
"""
|
| 78 |
+
Return all configuration options.
|
| 79 |
+
|
| 80 |
+
Returns:
|
| 81 |
+
All configuration options.
|
| 82 |
+
"""
|
| 83 |
+
return {key: self.getConfig(key) for key in self.config.keys()}
|
| 84 |
+
|
| 85 |
+
def getConfigInfo(self) -> list[tuple[str, str]]:
|
| 86 |
+
"""
|
| 87 |
+
Return descriptions of all configuration options.
|
| 88 |
+
|
| 89 |
+
Returns:
|
| 90 |
+
All descriptions of configuration options.
|
| 91 |
+
"""
|
| 92 |
+
return [(key, self.config[key][1]) for key in self.config.keys()]
|
| 93 |
+
|
| 94 |
+
def setConfig(self, key: str, value: Any) -> None:
|
| 95 |
+
"""
|
| 96 |
+
Set a configuration option.
|
| 97 |
+
|
| 98 |
+
If the corresponding default value set in [`config`][markdown.extensions.Extension.config]
|
| 99 |
+
is a `bool` value or `None`, then `value` is passed through
|
| 100 |
+
[`parseBoolValue`][markdown.util.parseBoolValue] before being stored.
|
| 101 |
+
|
| 102 |
+
Arguments:
|
| 103 |
+
key: Name of configuration option to set.
|
| 104 |
+
value: Value to assign to option.
|
| 105 |
+
|
| 106 |
+
Raises:
|
| 107 |
+
KeyError: If `key` is not known.
|
| 108 |
+
"""
|
| 109 |
+
if isinstance(self.config[key][0], bool):
|
| 110 |
+
value = parseBoolValue(value)
|
| 111 |
+
if self.config[key][0] is None:
|
| 112 |
+
value = parseBoolValue(value, preserve_none=True)
|
| 113 |
+
self.config[key][0] = value
|
| 114 |
+
|
| 115 |
+
def setConfigs(self, items: Mapping[str, Any] | Iterable[tuple[str, Any]]) -> None:
|
| 116 |
+
"""
|
| 117 |
+
Loop through a collection of configuration options, passing each to
|
| 118 |
+
[`setConfig`][markdown.extensions.Extension.setConfig].
|
| 119 |
+
|
| 120 |
+
Arguments:
|
| 121 |
+
items: Collection of configuration options.
|
| 122 |
+
|
| 123 |
+
Raises:
|
| 124 |
+
KeyError: for any unknown key.
|
| 125 |
+
"""
|
| 126 |
+
if hasattr(items, 'items'):
|
| 127 |
+
# it's a dict
|
| 128 |
+
items = items.items()
|
| 129 |
+
for key, value in items:
|
| 130 |
+
self.setConfig(key, value)
|
| 131 |
+
|
| 132 |
+
def extendMarkdown(self, md: Markdown) -> None:
|
| 133 |
+
"""
|
| 134 |
+
Add the various processors and patterns to the Markdown Instance.
|
| 135 |
+
|
| 136 |
+
This method must be overridden by every extension.
|
| 137 |
+
|
| 138 |
+
Arguments:
|
| 139 |
+
md: The Markdown instance.
|
| 140 |
+
|
| 141 |
+
"""
|
| 142 |
+
raise NotImplementedError(
|
| 143 |
+
'Extension "%s.%s" must define an "extendMarkdown"'
|
| 144 |
+
'method.' % (self.__class__.__module__, self.__class__.__name__)
|
| 145 |
+
)
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (5.27 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/abbr.cpython-312.pyc
ADDED
|
Binary file (11 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/admonition.cpython-312.pyc
ADDED
|
Binary file (6.93 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/attr_list.cpython-312.pyc
ADDED
|
Binary file (9.27 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/codehilite.cpython-312.pyc
ADDED
|
Binary file (14.6 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/def_list.cpython-312.pyc
ADDED
|
Binary file (5.41 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/extra.cpython-312.pyc
ADDED
|
Binary file (2.38 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/fenced_code.cpython-312.pyc
ADDED
|
Binary file (8.79 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/footnotes.cpython-312.pyc
ADDED
|
Binary file (25.4 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_attrs.cpython-312.pyc
ADDED
|
Binary file (3.59 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_em.cpython-312.pyc
ADDED
|
Binary file (2.52 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/md_in_html.cpython-312.pyc
ADDED
|
Binary file (21.3 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/meta.cpython-312.pyc
ADDED
|
Binary file (3.61 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/nl2br.cpython-312.pyc
ADDED
|
Binary file (1.31 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/sane_lists.cpython-312.pyc
ADDED
|
Binary file (2.91 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/smarty.cpython-312.pyc
ADDED
|
Binary file (8.89 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/tables.cpython-312.pyc
ADDED
|
Binary file (10.2 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/toc.cpython-312.pyc
ADDED
|
Binary file (22.2 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/__pycache__/wikilinks.cpython-312.pyc
ADDED
|
Binary file (4.81 kB). View file
|
|
|
lib/python3.12/site-packages/markdown/extensions/abbr.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Abbreviation Extension for Python-Markdown
|
| 2 |
+
# ==========================================
|
| 3 |
+
|
| 4 |
+
# This extension adds abbreviation handling to Python-Markdown.
|
| 5 |
+
|
| 6 |
+
# See https://Python-Markdown.github.io/extensions/abbreviations
|
| 7 |
+
# for documentation.
|
| 8 |
+
|
| 9 |
+
# Original code Copyright 2007-2008 [Waylan Limberg](https://github.com/waylan)
|
| 10 |
+
# and [Seemant Kulleen](http://www.kulleen.org/)
|
| 11 |
+
|
| 12 |
+
# All changes Copyright 2008-2014 The Python Markdown Project
|
| 13 |
+
|
| 14 |
+
# License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
This extension adds abbreviation handling to Python-Markdown.
|
| 18 |
+
|
| 19 |
+
See the [documentation](https://Python-Markdown.github.io/extensions/abbreviations)
|
| 20 |
+
for details.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
from . import Extension
|
| 26 |
+
from ..blockprocessors import BlockProcessor
|
| 27 |
+
from ..inlinepatterns import InlineProcessor
|
| 28 |
+
from ..treeprocessors import Treeprocessor
|
| 29 |
+
from ..util import AtomicString, deprecated
|
| 30 |
+
from typing import TYPE_CHECKING
|
| 31 |
+
import re
|
| 32 |
+
import xml.etree.ElementTree as etree
|
| 33 |
+
|
| 34 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 35 |
+
from .. import Markdown
|
| 36 |
+
from ..blockparser import BlockParser
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class AbbrExtension(Extension):
|
| 40 |
+
""" Abbreviation Extension for Python-Markdown. """
|
| 41 |
+
|
| 42 |
+
def __init__(self, **kwargs):
|
| 43 |
+
""" Initiate Extension and set up configs. """
|
| 44 |
+
self.config = {
|
| 45 |
+
'glossary': [
|
| 46 |
+
{},
|
| 47 |
+
'A dictionary where the `key` is the abbreviation and the `value` is the definition.'
|
| 48 |
+
"Default: `{}`"
|
| 49 |
+
],
|
| 50 |
+
}
|
| 51 |
+
""" Default configuration options. """
|
| 52 |
+
super().__init__(**kwargs)
|
| 53 |
+
self.abbrs = {}
|
| 54 |
+
self.glossary = {}
|
| 55 |
+
|
| 56 |
+
def reset(self):
|
| 57 |
+
""" Clear all previously defined abbreviations. """
|
| 58 |
+
self.abbrs.clear()
|
| 59 |
+
if (self.glossary):
|
| 60 |
+
self.abbrs.update(self.glossary)
|
| 61 |
+
|
| 62 |
+
def reset_glossary(self):
|
| 63 |
+
""" Clear all abbreviations from the glossary. """
|
| 64 |
+
self.glossary.clear()
|
| 65 |
+
|
| 66 |
+
def load_glossary(self, dictionary: dict[str, str]):
|
| 67 |
+
"""Adds `dictionary` to our glossary. Any abbreviations that already exist will be overwritten."""
|
| 68 |
+
if dictionary:
|
| 69 |
+
self.glossary = {**dictionary, **self.glossary}
|
| 70 |
+
|
| 71 |
+
def extendMarkdown(self, md):
|
| 72 |
+
""" Insert `AbbrTreeprocessor` and `AbbrBlockprocessor`. """
|
| 73 |
+
if (self.config['glossary'][0]):
|
| 74 |
+
self.load_glossary(self.config['glossary'][0])
|
| 75 |
+
self.abbrs.update(self.glossary)
|
| 76 |
+
md.registerExtension(self)
|
| 77 |
+
md.treeprocessors.register(AbbrTreeprocessor(md, self.abbrs), 'abbr', 7)
|
| 78 |
+
md.parser.blockprocessors.register(AbbrBlockprocessor(md.parser, self.abbrs), 'abbr', 16)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class AbbrTreeprocessor(Treeprocessor):
|
| 82 |
+
""" Replace abbreviation text with `<abbr>` elements. """
|
| 83 |
+
|
| 84 |
+
def __init__(self, md: Markdown | None = None, abbrs: dict | None = None):
|
| 85 |
+
self.abbrs: dict = abbrs if abbrs is not None else {}
|
| 86 |
+
self.RE: re.RegexObject | None = None
|
| 87 |
+
super().__init__(md)
|
| 88 |
+
|
| 89 |
+
def create_element(self, title: str, text: str, tail: str) -> etree.Element:
|
| 90 |
+
''' Create an `abbr` element. '''
|
| 91 |
+
abbr = etree.Element('abbr', {'title': title})
|
| 92 |
+
abbr.text = AtomicString(text)
|
| 93 |
+
abbr.tail = tail
|
| 94 |
+
return abbr
|
| 95 |
+
|
| 96 |
+
def iter_element(self, el: etree.Element, parent: etree.Element | None = None) -> None:
|
| 97 |
+
''' Recursively iterate over elements, run regex on text and wrap matches in `abbr` tags. '''
|
| 98 |
+
for child in reversed(el):
|
| 99 |
+
self.iter_element(child, el)
|
| 100 |
+
if text := el.text:
|
| 101 |
+
if not isinstance(text, AtomicString):
|
| 102 |
+
for m in reversed(list(self.RE.finditer(text))):
|
| 103 |
+
if self.abbrs[m.group(0)]:
|
| 104 |
+
abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), text[m.end():])
|
| 105 |
+
el.insert(0, abbr)
|
| 106 |
+
text = text[:m.start()]
|
| 107 |
+
el.text = text
|
| 108 |
+
if parent is not None and el.tail:
|
| 109 |
+
tail = el.tail
|
| 110 |
+
index = list(parent).index(el) + 1
|
| 111 |
+
if not isinstance(tail, AtomicString):
|
| 112 |
+
for m in reversed(list(self.RE.finditer(tail))):
|
| 113 |
+
abbr = self.create_element(self.abbrs[m.group(0)], m.group(0), tail[m.end():])
|
| 114 |
+
parent.insert(index, abbr)
|
| 115 |
+
tail = tail[:m.start()]
|
| 116 |
+
el.tail = tail
|
| 117 |
+
|
| 118 |
+
def run(self, root: etree.Element) -> etree.Element | None:
|
| 119 |
+
''' Step through tree to find known abbreviations. '''
|
| 120 |
+
if not self.abbrs:
|
| 121 |
+
# No abbreviations defined. Skip running processor.
|
| 122 |
+
return
|
| 123 |
+
# Build and compile regex
|
| 124 |
+
abbr_list = list(self.abbrs.keys())
|
| 125 |
+
abbr_list.sort(key=len, reverse=True)
|
| 126 |
+
self.RE = re.compile(f"\\b(?:{ '|'.join(re.escape(key) for key in abbr_list) })\\b")
|
| 127 |
+
# Step through tree and modify on matches
|
| 128 |
+
self.iter_element(root)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class AbbrBlockprocessor(BlockProcessor):
|
| 132 |
+
""" Parse text for abbreviation references. """
|
| 133 |
+
|
| 134 |
+
RE = re.compile(r'^[*]\[(?P<abbr>[^\\]*?)\][ ]?:[ ]*\n?[ ]*(?P<title>.*)$', re.MULTILINE)
|
| 135 |
+
|
| 136 |
+
def __init__(self, parser: BlockParser, abbrs: dict):
|
| 137 |
+
self.abbrs: dict = abbrs
|
| 138 |
+
super().__init__(parser)
|
| 139 |
+
|
| 140 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 141 |
+
return True
|
| 142 |
+
|
| 143 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> bool:
|
| 144 |
+
"""
|
| 145 |
+
Find and remove all abbreviation references from the text.
|
| 146 |
+
Each reference is added to the abbreviation collection.
|
| 147 |
+
|
| 148 |
+
"""
|
| 149 |
+
block = blocks.pop(0)
|
| 150 |
+
m = self.RE.search(block)
|
| 151 |
+
if m:
|
| 152 |
+
abbr = m.group('abbr').strip()
|
| 153 |
+
title = m.group('title').strip()
|
| 154 |
+
if title and abbr:
|
| 155 |
+
if title == "''" or title == '""':
|
| 156 |
+
self.abbrs.pop(abbr)
|
| 157 |
+
else:
|
| 158 |
+
self.abbrs[abbr] = title
|
| 159 |
+
if block[m.end():].strip():
|
| 160 |
+
# Add any content after match back to blocks as separate block
|
| 161 |
+
blocks.insert(0, block[m.end():].lstrip('\n'))
|
| 162 |
+
if block[:m.start()].strip():
|
| 163 |
+
# Add any content before match back to blocks as separate block
|
| 164 |
+
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
| 165 |
+
return True
|
| 166 |
+
# No match. Restore block.
|
| 167 |
+
blocks.insert(0, block)
|
| 168 |
+
return False
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
AbbrPreprocessor = deprecated("This class has been renamed to `AbbrBlockprocessor`.")(AbbrBlockprocessor)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
@deprecated("This class will be removed in the future; use `AbbrTreeprocessor` instead.")
|
| 175 |
+
class AbbrInlineProcessor(InlineProcessor):
|
| 176 |
+
""" Abbreviation inline pattern. """
|
| 177 |
+
|
| 178 |
+
def __init__(self, pattern: str, title: str):
|
| 179 |
+
super().__init__(pattern)
|
| 180 |
+
self.title = title
|
| 181 |
+
|
| 182 |
+
def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element, int, int]:
|
| 183 |
+
abbr = etree.Element('abbr')
|
| 184 |
+
abbr.text = AtomicString(m.group('abbr'))
|
| 185 |
+
abbr.set('title', self.title)
|
| 186 |
+
return abbr, m.start(0), m.end(0)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def makeExtension(**kwargs): # pragma: no cover
|
| 190 |
+
return AbbrExtension(**kwargs)
|
lib/python3.12/site-packages/markdown/extensions/admonition.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Admonition extension for Python-Markdown
|
| 2 |
+
# ========================================
|
| 3 |
+
|
| 4 |
+
# Adds rST-style admonitions. Inspired by [rST][] feature with the same name.
|
| 5 |
+
|
| 6 |
+
# [rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
|
| 7 |
+
|
| 8 |
+
# See https://Python-Markdown.github.io/extensions/admonition
|
| 9 |
+
# for documentation.
|
| 10 |
+
|
| 11 |
+
# Original code Copyright [Tiago Serafim](https://www.tiagoserafim.com/).
|
| 12 |
+
|
| 13 |
+
# All changes Copyright The Python Markdown Project
|
| 14 |
+
|
| 15 |
+
# License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
"""
|
| 19 |
+
Adds rST-style admonitions to Python-Markdown.
|
| 20 |
+
Inspired by [rST][] feature with the same name.
|
| 21 |
+
|
| 22 |
+
[rST]: http://docutils.sourceforge.net/docs/ref/rst/directives.html#specific-admonitions
|
| 23 |
+
|
| 24 |
+
See the [documentation](https://Python-Markdown.github.io/extensions/admonition)
|
| 25 |
+
for details.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
from . import Extension
|
| 31 |
+
from ..blockprocessors import BlockProcessor
|
| 32 |
+
import xml.etree.ElementTree as etree
|
| 33 |
+
import re
|
| 34 |
+
from typing import TYPE_CHECKING
|
| 35 |
+
|
| 36 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 37 |
+
from markdown import blockparser
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class AdmonitionExtension(Extension):
|
| 41 |
+
""" Admonition extension for Python-Markdown. """
|
| 42 |
+
|
| 43 |
+
def extendMarkdown(self, md):
|
| 44 |
+
""" Add Admonition to Markdown instance. """
|
| 45 |
+
md.registerExtension(self)
|
| 46 |
+
|
| 47 |
+
md.parser.blockprocessors.register(AdmonitionProcessor(md.parser), 'admonition', 105)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class AdmonitionProcessor(BlockProcessor):
|
| 51 |
+
|
| 52 |
+
CLASSNAME = 'admonition'
|
| 53 |
+
CLASSNAME_TITLE = 'admonition-title'
|
| 54 |
+
RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
|
| 55 |
+
RE_SPACES = re.compile(' +')
|
| 56 |
+
|
| 57 |
+
def __init__(self, parser: blockparser.BlockParser):
|
| 58 |
+
"""Initialization."""
|
| 59 |
+
|
| 60 |
+
super().__init__(parser)
|
| 61 |
+
|
| 62 |
+
self.current_sibling: etree.Element | None = None
|
| 63 |
+
self.content_indent = 0
|
| 64 |
+
|
| 65 |
+
def parse_content(self, parent: etree.Element, block: str) -> tuple[etree.Element | None, str, str]:
|
| 66 |
+
"""Get sibling admonition.
|
| 67 |
+
|
| 68 |
+
Retrieve the appropriate sibling element. This can get tricky when
|
| 69 |
+
dealing with lists.
|
| 70 |
+
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
old_block = block
|
| 74 |
+
the_rest = ''
|
| 75 |
+
|
| 76 |
+
# We already acquired the block via test
|
| 77 |
+
if self.current_sibling is not None:
|
| 78 |
+
sibling = self.current_sibling
|
| 79 |
+
block, the_rest = self.detab(block, self.content_indent)
|
| 80 |
+
self.current_sibling = None
|
| 81 |
+
self.content_indent = 0
|
| 82 |
+
return sibling, block, the_rest
|
| 83 |
+
|
| 84 |
+
sibling = self.lastChild(parent)
|
| 85 |
+
|
| 86 |
+
if sibling is None or sibling.tag != 'div' or sibling.get('class', '').find(self.CLASSNAME) == -1:
|
| 87 |
+
sibling = None
|
| 88 |
+
else:
|
| 89 |
+
# If the last child is a list and the content is sufficiently indented
|
| 90 |
+
# to be under it, then the content's sibling is in the list.
|
| 91 |
+
last_child = self.lastChild(sibling)
|
| 92 |
+
indent = 0
|
| 93 |
+
while last_child is not None:
|
| 94 |
+
if (
|
| 95 |
+
sibling is not None and block.startswith(' ' * self.tab_length * 2) and
|
| 96 |
+
last_child is not None and last_child.tag in ('ul', 'ol', 'dl')
|
| 97 |
+
):
|
| 98 |
+
|
| 99 |
+
# The expectation is that we'll find an `<li>` or `<dt>`.
|
| 100 |
+
# We should get its last child as well.
|
| 101 |
+
sibling = self.lastChild(last_child)
|
| 102 |
+
last_child = self.lastChild(sibling) if sibling is not None else None
|
| 103 |
+
|
| 104 |
+
# Context has been lost at this point, so we must adjust the
|
| 105 |
+
# text's indentation level so it will be evaluated correctly
|
| 106 |
+
# under the list.
|
| 107 |
+
block = block[self.tab_length:]
|
| 108 |
+
indent += self.tab_length
|
| 109 |
+
else:
|
| 110 |
+
last_child = None
|
| 111 |
+
|
| 112 |
+
if not block.startswith(' ' * self.tab_length):
|
| 113 |
+
sibling = None
|
| 114 |
+
|
| 115 |
+
if sibling is not None:
|
| 116 |
+
indent += self.tab_length
|
| 117 |
+
block, the_rest = self.detab(old_block, indent)
|
| 118 |
+
self.current_sibling = sibling
|
| 119 |
+
self.content_indent = indent
|
| 120 |
+
|
| 121 |
+
return sibling, block, the_rest
|
| 122 |
+
|
| 123 |
+
def test(self, parent: etree.Element, block: str) -> bool:
|
| 124 |
+
|
| 125 |
+
if self.RE.search(block):
|
| 126 |
+
return True
|
| 127 |
+
else:
|
| 128 |
+
return self.parse_content(parent, block)[0] is not None
|
| 129 |
+
|
| 130 |
+
def run(self, parent: etree.Element, blocks: list[str]) -> None:
|
| 131 |
+
block = blocks.pop(0)
|
| 132 |
+
m = self.RE.search(block)
|
| 133 |
+
|
| 134 |
+
if m:
|
| 135 |
+
if m.start() > 0:
|
| 136 |
+
self.parser.parseBlocks(parent, [block[:m.start()]])
|
| 137 |
+
block = block[m.end():] # removes the first line
|
| 138 |
+
block, theRest = self.detab(block)
|
| 139 |
+
else:
|
| 140 |
+
sibling, block, theRest = self.parse_content(parent, block)
|
| 141 |
+
|
| 142 |
+
if m:
|
| 143 |
+
klass, title = self.get_class_and_title(m)
|
| 144 |
+
div = etree.SubElement(parent, 'div')
|
| 145 |
+
div.set('class', '{} {}'.format(self.CLASSNAME, klass))
|
| 146 |
+
if title:
|
| 147 |
+
p = etree.SubElement(div, 'p')
|
| 148 |
+
p.text = title
|
| 149 |
+
p.set('class', self.CLASSNAME_TITLE)
|
| 150 |
+
else:
|
| 151 |
+
# Sibling is a list item, but we need to wrap it's content should be wrapped in <p>
|
| 152 |
+
if sibling.tag in ('li', 'dd') and sibling.text:
|
| 153 |
+
text = sibling.text
|
| 154 |
+
sibling.text = ''
|
| 155 |
+
p = etree.SubElement(sibling, 'p')
|
| 156 |
+
p.text = text
|
| 157 |
+
|
| 158 |
+
div = sibling
|
| 159 |
+
|
| 160 |
+
self.parser.parseChunk(div, block)
|
| 161 |
+
|
| 162 |
+
if theRest:
|
| 163 |
+
# This block contained unindented line(s) after the first indented
|
| 164 |
+
# line. Insert these lines as the first block of the master blocks
|
| 165 |
+
# list for future processing.
|
| 166 |
+
blocks.insert(0, theRest)
|
| 167 |
+
|
| 168 |
+
def get_class_and_title(self, match: re.Match[str]) -> tuple[str, str | None]:
|
| 169 |
+
klass, title = match.group(1).lower(), match.group(2)
|
| 170 |
+
klass = self.RE_SPACES.sub(' ', klass)
|
| 171 |
+
if title is None:
|
| 172 |
+
# no title was provided, use the capitalized class name as title
|
| 173 |
+
# e.g.: `!!! note` will render
|
| 174 |
+
# `<p class="admonition-title">Note</p>`
|
| 175 |
+
title = klass.split(' ', 1)[0].capitalize()
|
| 176 |
+
elif title == '':
|
| 177 |
+
# an explicit blank title should not be rendered
|
| 178 |
+
# e.g.: `!!! warning ""` will *not* render `p` with a title
|
| 179 |
+
title = None
|
| 180 |
+
return klass, title
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def makeExtension(**kwargs): # pragma: no cover
|
| 184 |
+
return AdmonitionExtension(**kwargs)
|
lib/python3.12/site-packages/markdown/extensions/attr_list.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Attribute List Extension for Python-Markdown
|
| 2 |
+
# ============================================
|
| 3 |
+
|
| 4 |
+
# Adds attribute list syntax. Inspired by
|
| 5 |
+
# [Maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s
|
| 6 |
+
# feature of the same name.
|
| 7 |
+
|
| 8 |
+
# See https://Python-Markdown.github.io/extensions/attr_list
|
| 9 |
+
# for documentation.
|
| 10 |
+
|
| 11 |
+
# Original code Copyright 2011 [Waylan Limberg](https://github.com/waylan).
|
| 12 |
+
|
| 13 |
+
# All changes Copyright 2011-2014 The Python Markdown Project
|
| 14 |
+
|
| 15 |
+
# License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
| 16 |
+
|
| 17 |
+
"""
|
| 18 |
+
Adds attribute list syntax to Python-Markdown.
|
| 19 |
+
Inspired by
|
| 20 |
+
[Maruku](http://maruku.rubyforge.org/proposal.html#attribute_lists)'s
|
| 21 |
+
feature of the same name.
|
| 22 |
+
|
| 23 |
+
See the [documentation](https://Python-Markdown.github.io/extensions/attr_list)
|
| 24 |
+
for details.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
from __future__ import annotations
|
| 28 |
+
from typing import TYPE_CHECKING
|
| 29 |
+
|
| 30 |
+
from . import Extension
|
| 31 |
+
from ..treeprocessors import Treeprocessor
|
| 32 |
+
import re
|
| 33 |
+
|
| 34 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 35 |
+
from xml.etree.ElementTree import Element
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _handle_double_quote(s, t):
|
| 39 |
+
k, v = t.split('=', 1)
|
| 40 |
+
return k, v.strip('"')
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def _handle_single_quote(s, t):
|
| 44 |
+
k, v = t.split('=', 1)
|
| 45 |
+
return k, v.strip("'")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _handle_key_value(s, t):
|
| 49 |
+
return t.split('=', 1)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _handle_word(s, t):
|
| 53 |
+
if t.startswith('.'):
|
| 54 |
+
return '.', t[1:]
|
| 55 |
+
if t.startswith('#'):
|
| 56 |
+
return 'id', t[1:]
|
| 57 |
+
return t, t
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
_scanner = re.Scanner([
|
| 61 |
+
(r'[^ =}]+=".*?"', _handle_double_quote),
|
| 62 |
+
(r"[^ =}]+='.*?'", _handle_single_quote),
|
| 63 |
+
(r'[^ =}]+=[^ =}]+', _handle_key_value),
|
| 64 |
+
(r'[^ =}]+', _handle_word),
|
| 65 |
+
(r' ', None)
|
| 66 |
+
])
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def get_attrs_and_remainder(attrs_string: str) -> tuple[list[tuple[str, str]], str]:
|
| 70 |
+
""" Parse attribute list and return a list of attribute tuples.
|
| 71 |
+
|
| 72 |
+
Additionally, return any text that remained after a curly brace. In typical cases, its presence
|
| 73 |
+
should mean that the input does not match the intended attribute list syntax.
|
| 74 |
+
"""
|
| 75 |
+
attrs, remainder = _scanner.scan(attrs_string)
|
| 76 |
+
# To keep historic behavior, discard all unparsable text prior to '}'.
|
| 77 |
+
index = remainder.find('}')
|
| 78 |
+
remainder = remainder[index:] if index != -1 else ''
|
| 79 |
+
return attrs, remainder
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def get_attrs(str: str) -> list[tuple[str, str]]: # pragma: no cover
|
| 83 |
+
""" Soft-deprecated. Prefer `get_attrs_and_remainder`. """
|
| 84 |
+
return get_attrs_and_remainder(str)[0]
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def isheader(elem: Element) -> bool:
|
| 88 |
+
return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class AttrListTreeprocessor(Treeprocessor):
|
| 92 |
+
|
| 93 |
+
BASE_RE = r'\{\:?[ ]*([^\}\n ][^\n]*)[ ]*\}'
|
| 94 |
+
HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE))
|
| 95 |
+
BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE))
|
| 96 |
+
INLINE_RE = re.compile(r'^{}'.format(BASE_RE))
|
| 97 |
+
NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff'
|
| 98 |
+
r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d'
|
| 99 |
+
r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff'
|
| 100 |
+
r'\uf900-\ufdcf\ufdf0-\ufffd'
|
| 101 |
+
r'\:\-\.0-9\u00b7\u0300-\u036f\u203f-\u2040]+')
|
| 102 |
+
|
| 103 |
+
def run(self, doc: Element) -> None:
|
| 104 |
+
for elem in doc.iter():
|
| 105 |
+
if self.md.is_block_level(elem.tag):
|
| 106 |
+
# Block level: check for `attrs` on last line of text
|
| 107 |
+
RE = self.BLOCK_RE
|
| 108 |
+
if isheader(elem) or elem.tag in ['dt', 'td', 'th']:
|
| 109 |
+
# header, def-term, or table cell: check for attributes at end of element
|
| 110 |
+
RE = self.HEADER_RE
|
| 111 |
+
if len(elem) and elem.tag == 'li':
|
| 112 |
+
# special case list items. children may include a `ul` or `ol`.
|
| 113 |
+
pos = None
|
| 114 |
+
# find the `ul` or `ol` position
|
| 115 |
+
for i, child in enumerate(elem):
|
| 116 |
+
if child.tag in ['ul', 'ol']:
|
| 117 |
+
pos = i
|
| 118 |
+
break
|
| 119 |
+
if pos is None and elem[-1].tail:
|
| 120 |
+
# use tail of last child. no `ul` or `ol`.
|
| 121 |
+
m = RE.search(elem[-1].tail)
|
| 122 |
+
if m:
|
| 123 |
+
if not self.assign_attrs(elem, m.group(1), strict=True):
|
| 124 |
+
elem[-1].tail = elem[-1].tail[:m.start()]
|
| 125 |
+
elif pos is not None and pos > 0 and elem[pos-1].tail:
|
| 126 |
+
# use tail of last child before `ul` or `ol`
|
| 127 |
+
m = RE.search(elem[pos-1].tail)
|
| 128 |
+
if m:
|
| 129 |
+
if not self.assign_attrs(elem, m.group(1), strict=True):
|
| 130 |
+
elem[pos-1].tail = elem[pos-1].tail[:m.start()]
|
| 131 |
+
elif elem.text:
|
| 132 |
+
# use text. `ul` is first child.
|
| 133 |
+
m = RE.search(elem.text)
|
| 134 |
+
if m:
|
| 135 |
+
if not self.assign_attrs(elem, m.group(1), strict=True):
|
| 136 |
+
elem.text = elem.text[:m.start()]
|
| 137 |
+
elif len(elem) and elem[-1].tail:
|
| 138 |
+
# has children. Get from tail of last child
|
| 139 |
+
m = RE.search(elem[-1].tail)
|
| 140 |
+
if m:
|
| 141 |
+
if not self.assign_attrs(elem, m.group(1), strict=True):
|
| 142 |
+
elem[-1].tail = elem[-1].tail[:m.start()]
|
| 143 |
+
if isheader(elem):
|
| 144 |
+
# clean up trailing #s
|
| 145 |
+
elem[-1].tail = elem[-1].tail.rstrip('#').rstrip()
|
| 146 |
+
elif elem.text:
|
| 147 |
+
# no children. Get from text.
|
| 148 |
+
m = RE.search(elem.text)
|
| 149 |
+
if m:
|
| 150 |
+
if not self.assign_attrs(elem, m.group(1), strict=True):
|
| 151 |
+
elem.text = elem.text[:m.start()]
|
| 152 |
+
if isheader(elem):
|
| 153 |
+
# clean up trailing #s
|
| 154 |
+
elem.text = elem.text.rstrip('#').rstrip()
|
| 155 |
+
else:
|
| 156 |
+
# inline: check for `attrs` at start of tail
|
| 157 |
+
if elem.tail:
|
| 158 |
+
m = self.INLINE_RE.match(elem.tail)
|
| 159 |
+
if m:
|
| 160 |
+
remainder = self.assign_attrs(elem, m.group(1))
|
| 161 |
+
elem.tail = elem.tail[m.end():] + remainder
|
| 162 |
+
|
| 163 |
+
def assign_attrs(self, elem: Element, attrs_string: str, *, strict: bool = False) -> str:
|
| 164 |
+
""" Assign `attrs` to element.
|
| 165 |
+
|
| 166 |
+
If the `attrs_string` has an extra closing curly brace, the remaining text is returned.
|
| 167 |
+
|
| 168 |
+
The `strict` argument controls whether to still assign `attrs` if there is a remaining `}`.
|
| 169 |
+
"""
|
| 170 |
+
attrs, remainder = get_attrs_and_remainder(attrs_string)
|
| 171 |
+
if strict and remainder:
|
| 172 |
+
return remainder
|
| 173 |
+
|
| 174 |
+
for k, v in attrs:
|
| 175 |
+
if k == '.':
|
| 176 |
+
# add to class
|
| 177 |
+
cls = elem.get('class')
|
| 178 |
+
if cls:
|
| 179 |
+
elem.set('class', '{} {}'.format(cls, v))
|
| 180 |
+
else:
|
| 181 |
+
elem.set('class', v)
|
| 182 |
+
else:
|
| 183 |
+
# assign attribute `k` with `v`
|
| 184 |
+
elem.set(self.sanitize_name(k), v)
|
| 185 |
+
# The text that we initially over-matched will be put back.
|
| 186 |
+
return remainder
|
| 187 |
+
|
| 188 |
+
def sanitize_name(self, name: str) -> str:
|
| 189 |
+
"""
|
| 190 |
+
Sanitize name as 'an XML Name, minus the `:`.'
|
| 191 |
+
See <https://www.w3.org/TR/REC-xml-names/#NT-NCName>.
|
| 192 |
+
"""
|
| 193 |
+
return self.NAME_RE.sub('_', name)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
class AttrListExtension(Extension):
|
| 197 |
+
""" Attribute List extension for Python-Markdown """
|
| 198 |
+
def extendMarkdown(self, md):
|
| 199 |
+
md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
|
| 200 |
+
md.registerExtension(self)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def makeExtension(**kwargs): # pragma: no cover
|
| 204 |
+
return AttrListExtension(**kwargs)
|
lib/python3.12/site-packages/markdown/extensions/codehilite.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CodeHilite Extension for Python-Markdown
|
| 2 |
+
# ========================================
|
| 3 |
+
|
| 4 |
+
# Adds code/syntax highlighting to standard Python-Markdown code blocks.
|
| 5 |
+
|
| 6 |
+
# See https://Python-Markdown.github.io/extensions/code_hilite
|
| 7 |
+
# for documentation.
|
| 8 |
+
|
| 9 |
+
# Original code Copyright 2006-2008 [Waylan Limberg](https://github.com/waylan).
|
| 10 |
+
|
| 11 |
+
# All changes Copyright 2008-2014 The Python Markdown Project
|
| 12 |
+
|
| 13 |
+
# License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Adds code/syntax highlighting to standard Python-Markdown code blocks.
|
| 17 |
+
|
| 18 |
+
See the [documentation](https://Python-Markdown.github.io/extensions/code_hilite)
|
| 19 |
+
for details.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
from . import Extension
|
| 25 |
+
from ..treeprocessors import Treeprocessor
|
| 26 |
+
from ..util import parseBoolValue
|
| 27 |
+
from typing import TYPE_CHECKING, Callable, Any
|
| 28 |
+
|
| 29 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 30 |
+
import xml.etree.ElementTree as etree
|
| 31 |
+
|
| 32 |
+
try: # pragma: no cover
|
| 33 |
+
from pygments import highlight
|
| 34 |
+
from pygments.lexers import get_lexer_by_name, guess_lexer
|
| 35 |
+
from pygments.formatters import get_formatter_by_name
|
| 36 |
+
from pygments.util import ClassNotFound
|
| 37 |
+
pygments = True
|
| 38 |
+
except ImportError: # pragma: no cover
|
| 39 |
+
pygments = False
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def parse_hl_lines(expr: str) -> list[int]:
|
| 43 |
+
"""Support our syntax for emphasizing certain lines of code.
|
| 44 |
+
|
| 45 |
+
`expr` should be like '1 2' to emphasize lines 1 and 2 of a code block.
|
| 46 |
+
Returns a list of integers, the line numbers to emphasize.
|
| 47 |
+
"""
|
| 48 |
+
if not expr:
|
| 49 |
+
return []
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
return list(map(int, expr.split()))
|
| 53 |
+
except ValueError: # pragma: no cover
|
| 54 |
+
return []
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# ------------------ The Main CodeHilite Class ----------------------
|
| 58 |
+
class CodeHilite:
|
| 59 |
+
"""
|
| 60 |
+
Determine language of source code, and pass it on to the Pygments highlighter.
|
| 61 |
+
|
| 62 |
+
Usage:
|
| 63 |
+
|
| 64 |
+
```python
|
| 65 |
+
code = CodeHilite(src=some_code, lang='python')
|
| 66 |
+
html = code.hilite()
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
Arguments:
|
| 70 |
+
src: Source string or any object with a `.readline` attribute.
|
| 71 |
+
|
| 72 |
+
Keyword arguments:
|
| 73 |
+
lang (str): String name of Pygments lexer to use for highlighting. Default: `None`.
|
| 74 |
+
guess_lang (bool): Auto-detect which lexer to use.
|
| 75 |
+
Ignored if `lang` is set to a valid value. Default: `True`.
|
| 76 |
+
use_pygments (bool): Pass code to Pygments for code highlighting. If `False`, the code is
|
| 77 |
+
instead wrapped for highlighting by a JavaScript library. Default: `True`.
|
| 78 |
+
pygments_formatter (str): The name of a Pygments formatter or a formatter class used for
|
| 79 |
+
highlighting the code blocks. Default: `html`.
|
| 80 |
+
linenums (bool): An alias to Pygments `linenos` formatter option. Default: `None`.
|
| 81 |
+
css_class (str): An alias to Pygments `cssclass` formatter option. Default: 'codehilite'.
|
| 82 |
+
lang_prefix (str): Prefix prepended to the language. Default: "language-".
|
| 83 |
+
|
| 84 |
+
Other Options:
|
| 85 |
+
|
| 86 |
+
Any other options are accepted and passed on to the lexer and formatter. Therefore,
|
| 87 |
+
valid options include any options which are accepted by the `html` formatter or
|
| 88 |
+
whichever lexer the code's language uses. Note that most lexers do not have any
|
| 89 |
+
options. However, a few have very useful options, such as PHP's `startinline` option.
|
| 90 |
+
Any invalid options are ignored without error.
|
| 91 |
+
|
| 92 |
+
* **Formatter options**: <https://pygments.org/docs/formatters/#HtmlFormatter>
|
| 93 |
+
* **Lexer Options**: <https://pygments.org/docs/lexers/>
|
| 94 |
+
|
| 95 |
+
Additionally, when Pygments is enabled, the code's language is passed to the
|
| 96 |
+
formatter as an extra option `lang_str`, whose value being `{lang_prefix}{lang}`.
|
| 97 |
+
This option has no effect to the Pygments' builtin formatters.
|
| 98 |
+
|
| 99 |
+
Advanced Usage:
|
| 100 |
+
|
| 101 |
+
```python
|
| 102 |
+
code = CodeHilite(
|
| 103 |
+
src = some_code,
|
| 104 |
+
lang = 'php',
|
| 105 |
+
startinline = True, # Lexer option. Snippet does not start with `<?php`.
|
| 106 |
+
linenostart = 42, # Formatter option. Snippet starts on line 42.
|
| 107 |
+
hl_lines = [45, 49, 50], # Formatter option. Highlight lines 45, 49, and 50.
|
| 108 |
+
linenos = 'inline' # Formatter option. Avoid alignment problems.
|
| 109 |
+
)
|
| 110 |
+
html = code.hilite()
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
def __init__(self, src: str, **options):
|
| 116 |
+
self.src = src
|
| 117 |
+
self.lang: str | None = options.pop('lang', None)
|
| 118 |
+
self.guess_lang: bool = options.pop('guess_lang', True)
|
| 119 |
+
self.use_pygments: bool = options.pop('use_pygments', True)
|
| 120 |
+
self.lang_prefix: str = options.pop('lang_prefix', 'language-')
|
| 121 |
+
self.pygments_formatter: str | Callable = options.pop('pygments_formatter', 'html')
|
| 122 |
+
|
| 123 |
+
if 'linenos' not in options:
|
| 124 |
+
options['linenos'] = options.pop('linenums', None)
|
| 125 |
+
if 'cssclass' not in options:
|
| 126 |
+
options['cssclass'] = options.pop('css_class', 'codehilite')
|
| 127 |
+
if 'wrapcode' not in options:
|
| 128 |
+
# Override Pygments default
|
| 129 |
+
options['wrapcode'] = True
|
| 130 |
+
# Disallow use of `full` option
|
| 131 |
+
options['full'] = False
|
| 132 |
+
|
| 133 |
+
self.options = options
|
| 134 |
+
|
| 135 |
+
def hilite(self, shebang: bool = True) -> str:
|
| 136 |
+
"""
|
| 137 |
+
Pass code to the [Pygments](https://pygments.org/) highlighter with
|
| 138 |
+
optional line numbers. The output should then be styled with CSS to
|
| 139 |
+
your liking. No styles are applied by default - only styling hooks
|
| 140 |
+
(i.e.: `<span class="k">`).
|
| 141 |
+
|
| 142 |
+
returns : A string of html.
|
| 143 |
+
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
self.src = self.src.strip('\n')
|
| 147 |
+
|
| 148 |
+
if self.lang is None and shebang:
|
| 149 |
+
self._parseHeader()
|
| 150 |
+
|
| 151 |
+
if pygments and self.use_pygments:
|
| 152 |
+
try:
|
| 153 |
+
lexer = get_lexer_by_name(self.lang, **self.options)
|
| 154 |
+
except ValueError:
|
| 155 |
+
try:
|
| 156 |
+
if self.guess_lang:
|
| 157 |
+
lexer = guess_lexer(self.src, **self.options)
|
| 158 |
+
else:
|
| 159 |
+
lexer = get_lexer_by_name('text', **self.options)
|
| 160 |
+
except ValueError: # pragma: no cover
|
| 161 |
+
lexer = get_lexer_by_name('text', **self.options)
|
| 162 |
+
if not self.lang:
|
| 163 |
+
# Use the guessed lexer's language instead
|
| 164 |
+
self.lang = lexer.aliases[0]
|
| 165 |
+
lang_str = f'{self.lang_prefix}{self.lang}'
|
| 166 |
+
if isinstance(self.pygments_formatter, str):
|
| 167 |
+
try:
|
| 168 |
+
formatter = get_formatter_by_name(self.pygments_formatter, **self.options)
|
| 169 |
+
except ClassNotFound:
|
| 170 |
+
formatter = get_formatter_by_name('html', **self.options)
|
| 171 |
+
else:
|
| 172 |
+
formatter = self.pygments_formatter(lang_str=lang_str, **self.options)
|
| 173 |
+
return highlight(self.src, lexer, formatter)
|
| 174 |
+
else:
|
| 175 |
+
# just escape and build markup usable by JavaScript highlighting libraries
|
| 176 |
+
txt = self.src.replace('&', '&')
|
| 177 |
+
txt = txt.replace('<', '<')
|
| 178 |
+
txt = txt.replace('>', '>')
|
| 179 |
+
txt = txt.replace('"', '"')
|
| 180 |
+
classes = []
|
| 181 |
+
if self.lang:
|
| 182 |
+
classes.append('{}{}'.format(self.lang_prefix, self.lang))
|
| 183 |
+
if self.options['linenos']:
|
| 184 |
+
classes.append('linenums')
|
| 185 |
+
class_str = ''
|
| 186 |
+
if classes:
|
| 187 |
+
class_str = ' class="{}"'.format(' '.join(classes))
|
| 188 |
+
return '<pre class="{}"><code{}>{}\n</code></pre>\n'.format(
|
| 189 |
+
self.options['cssclass'],
|
| 190 |
+
class_str,
|
| 191 |
+
txt
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
def _parseHeader(self) -> None:
|
| 195 |
+
"""
|
| 196 |
+
Determines language of a code block from shebang line and whether the
|
| 197 |
+
said line should be removed or left in place. If the shebang line
|
| 198 |
+
contains a path (even a single /) then it is assumed to be a real
|
| 199 |
+
shebang line and left alone. However, if no path is given
|
| 200 |
+
(e.i.: `#!python` or `:::python`) then it is assumed to be a mock shebang
|
| 201 |
+
for language identification of a code fragment and removed from the
|
| 202 |
+
code block prior to processing for code highlighting. When a mock
|
| 203 |
+
shebang (e.i: `#!python`) is found, line numbering is turned on. When
|
| 204 |
+
colons are found in place of a shebang (e.i.: `:::python`), line
|
| 205 |
+
numbering is left in the current state - off by default.
|
| 206 |
+
|
| 207 |
+
Also parses optional list of highlight lines, like:
|
| 208 |
+
|
| 209 |
+
:::python hl_lines="1 3"
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
import re
|
| 213 |
+
|
| 214 |
+
# split text into lines
|
| 215 |
+
lines = self.src.split("\n")
|
| 216 |
+
# pull first line to examine
|
| 217 |
+
fl = lines.pop(0)
|
| 218 |
+
|
| 219 |
+
c = re.compile(r'''
|
| 220 |
+
(?:(?:^::+)|(?P<shebang>^[#]!)) # Shebang or 2 or more colons
|
| 221 |
+
(?P<path>(?:/\w+)*[/ ])? # Zero or 1 path
|
| 222 |
+
(?P<lang>[\w#.+-]*) # The language
|
| 223 |
+
\s* # Arbitrary whitespace
|
| 224 |
+
# Optional highlight lines, single- or double-quote-delimited
|
| 225 |
+
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?
|
| 226 |
+
''', re.VERBOSE)
|
| 227 |
+
# search first line for shebang
|
| 228 |
+
m = c.search(fl)
|
| 229 |
+
if m:
|
| 230 |
+
# we have a match
|
| 231 |
+
try:
|
| 232 |
+
self.lang = m.group('lang').lower()
|
| 233 |
+
except IndexError: # pragma: no cover
|
| 234 |
+
self.lang = None
|
| 235 |
+
if m.group('path'):
|
| 236 |
+
# path exists - restore first line
|
| 237 |
+
lines.insert(0, fl)
|
| 238 |
+
if self.options['linenos'] is None and m.group('shebang'):
|
| 239 |
+
# Overridable and Shebang exists - use line numbers
|
| 240 |
+
self.options['linenos'] = True
|
| 241 |
+
|
| 242 |
+
self.options['hl_lines'] = parse_hl_lines(m.group('hl_lines'))
|
| 243 |
+
else:
|
| 244 |
+
# No match
|
| 245 |
+
lines.insert(0, fl)
|
| 246 |
+
|
| 247 |
+
self.src = "\n".join(lines).strip("\n")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# ------------------ The Markdown Extension -------------------------------
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class HiliteTreeprocessor(Treeprocessor):
|
| 254 |
+
""" Highlight source code in code blocks. """
|
| 255 |
+
|
| 256 |
+
config: dict[str, Any]
|
| 257 |
+
|
| 258 |
+
def code_unescape(self, text: str) -> str:
|
| 259 |
+
"""Unescape code."""
|
| 260 |
+
text = text.replace("<", "<")
|
| 261 |
+
text = text.replace(">", ">")
|
| 262 |
+
# Escaped '&' should be replaced at the end to avoid
|
| 263 |
+
# conflicting with < and >.
|
| 264 |
+
text = text.replace("&", "&")
|
| 265 |
+
return text
|
| 266 |
+
|
| 267 |
+
def run(self, root: etree.Element) -> None:
|
| 268 |
+
""" Find code blocks and store in `htmlStash`. """
|
| 269 |
+
blocks = root.iter('pre')
|
| 270 |
+
for block in blocks:
|
| 271 |
+
if len(block) == 1 and block[0].tag == 'code':
|
| 272 |
+
local_config = self.config.copy()
|
| 273 |
+
text = block[0].text
|
| 274 |
+
if text is None:
|
| 275 |
+
continue
|
| 276 |
+
code = CodeHilite(
|
| 277 |
+
self.code_unescape(text),
|
| 278 |
+
tab_length=self.md.tab_length,
|
| 279 |
+
style=local_config.pop('pygments_style', 'default'),
|
| 280 |
+
**local_config
|
| 281 |
+
)
|
| 282 |
+
placeholder = self.md.htmlStash.store(code.hilite())
|
| 283 |
+
# Clear code block in `etree` instance
|
| 284 |
+
block.clear()
|
| 285 |
+
# Change to `p` element which will later
|
| 286 |
+
# be removed when inserting raw html
|
| 287 |
+
block.tag = 'p'
|
| 288 |
+
block.text = placeholder
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class CodeHiliteExtension(Extension):
|
| 292 |
+
""" Add source code highlighting to markdown code blocks. """
|
| 293 |
+
|
| 294 |
+
def __init__(self, **kwargs):
|
| 295 |
+
# define default configs
|
| 296 |
+
self.config = {
|
| 297 |
+
'linenums': [
|
| 298 |
+
None, "Use lines numbers. True|table|inline=yes, False=no, None=auto. Default: `None`."
|
| 299 |
+
],
|
| 300 |
+
'guess_lang': [
|
| 301 |
+
True, "Automatic language detection - Default: `True`."
|
| 302 |
+
],
|
| 303 |
+
'css_class': [
|
| 304 |
+
"codehilite", "Set class name for wrapper <div> - Default: `codehilite`."
|
| 305 |
+
],
|
| 306 |
+
'pygments_style': [
|
| 307 |
+
'default', 'Pygments HTML Formatter Style (Colorscheme). Default: `default`.'
|
| 308 |
+
],
|
| 309 |
+
'noclasses': [
|
| 310 |
+
False, 'Use inline styles instead of CSS classes - Default `False`.'
|
| 311 |
+
],
|
| 312 |
+
'use_pygments': [
|
| 313 |
+
True, 'Highlight code blocks with pygments. Disable if using a JavaScript library. Default: `True`.'
|
| 314 |
+
],
|
| 315 |
+
'lang_prefix': [
|
| 316 |
+
'language-', 'Prefix prepended to the language when `use_pygments` is false. Default: `language-`.'
|
| 317 |
+
],
|
| 318 |
+
'pygments_formatter': [
|
| 319 |
+
'html', 'Use a specific formatter for Pygments highlighting. Default: `html`.'
|
| 320 |
+
],
|
| 321 |
+
}
|
| 322 |
+
""" Default configuration options. """
|
| 323 |
+
|
| 324 |
+
for key, value in kwargs.items():
|
| 325 |
+
if key in self.config:
|
| 326 |
+
self.setConfig(key, value)
|
| 327 |
+
else:
|
| 328 |
+
# manually set unknown keywords.
|
| 329 |
+
if isinstance(value, str):
|
| 330 |
+
try:
|
| 331 |
+
# Attempt to parse `str` as a boolean value
|
| 332 |
+
value = parseBoolValue(value, preserve_none=True)
|
| 333 |
+
except ValueError:
|
| 334 |
+
pass # Assume it's not a boolean value. Use as-is.
|
| 335 |
+
self.config[key] = [value, '']
|
| 336 |
+
|
| 337 |
+
def extendMarkdown(self, md):
|
| 338 |
+
""" Add `HilitePostprocessor` to Markdown instance. """
|
| 339 |
+
hiliter = HiliteTreeprocessor(md)
|
| 340 |
+
hiliter.config = self.getConfigs()
|
| 341 |
+
md.treeprocessors.register(hiliter, 'hilite', 30)
|
| 342 |
+
|
| 343 |
+
md.registerExtension(self)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def makeExtension(**kwargs): # pragma: no cover
|
| 347 |
+
return CodeHiliteExtension(**kwargs)
|