ZhengyangZhang commited on
Commit
4566c10
·
verified ·
1 Parent(s): fd22e58

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER +1 -0
  2. lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA +203 -0
  3. lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD +54 -0
  4. lib/python3.12/site-packages/httpx-0.28.1.dist-info/WHEEL +4 -0
  5. lib/python3.12/site-packages/httpx-0.28.1.dist-info/entry_points.txt +2 -0
  6. lib/python3.12/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md +12 -0
  7. lib/python3.12/site-packages/markdown/__init__.py +48 -0
  8. lib/python3.12/site-packages/markdown/__main__.py +150 -0
  9. lib/python3.12/site-packages/markdown/__meta__.py +51 -0
  10. lib/python3.12/site-packages/markdown/__pycache__/__init__.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/markdown/__pycache__/__main__.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/markdown/__pycache__/__meta__.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/markdown/__pycache__/blockparser.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/markdown/__pycache__/blockprocessors.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/markdown/__pycache__/core.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/markdown/__pycache__/htmlparser.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/markdown/__pycache__/inlinepatterns.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/markdown/__pycache__/postprocessors.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/markdown/__pycache__/preprocessors.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/markdown/__pycache__/serializers.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/markdown/__pycache__/test_tools.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/markdown/__pycache__/treeprocessors.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/markdown/__pycache__/util.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/markdown/blockparser.py +160 -0
  25. lib/python3.12/site-packages/markdown/blockprocessors.py +641 -0
  26. lib/python3.12/site-packages/markdown/core.py +503 -0
  27. lib/python3.12/site-packages/markdown/extensions/__init__.py +145 -0
  28. lib/python3.12/site-packages/markdown/extensions/__pycache__/__init__.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/markdown/extensions/__pycache__/abbr.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/markdown/extensions/__pycache__/admonition.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/markdown/extensions/__pycache__/attr_list.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/markdown/extensions/__pycache__/codehilite.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/markdown/extensions/__pycache__/def_list.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/markdown/extensions/__pycache__/extra.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/markdown/extensions/__pycache__/fenced_code.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/markdown/extensions/__pycache__/footnotes.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_attrs.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/markdown/extensions/__pycache__/legacy_em.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/markdown/extensions/__pycache__/md_in_html.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/markdown/extensions/__pycache__/meta.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/markdown/extensions/__pycache__/nl2br.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/markdown/extensions/__pycache__/sane_lists.cpython-312.pyc +0 -0
  43. lib/python3.12/site-packages/markdown/extensions/__pycache__/smarty.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/markdown/extensions/__pycache__/tables.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/markdown/extensions/__pycache__/toc.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/markdown/extensions/__pycache__/wikilinks.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/markdown/extensions/abbr.py +190 -0
  48. lib/python3.12/site-packages/markdown/extensions/admonition.py +184 -0
  49. lib/python3.12/site-packages/markdown/extensions/attr_list.py +204 -0
  50. 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/>&mdash; 🦋 &mdash;</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('&', '&amp;')
177
+ txt = txt.replace('<', '&lt;')
178
+ txt = txt.replace('>', '&gt;')
179
+ txt = txt.replace('"', '&quot;')
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("&lt;", "<")
261
+ text = text.replace("&gt;", ">")
262
+ # Escaped '&' should be replaced at the end to avoid
263
+ # conflicting with < and >.
264
+ text = text.replace("&amp;", "&")
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)