ZhengyangZhang commited on
Commit
b7beb76
·
verified ·
1 Parent(s): 8bc59a8

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/pip/_vendor/cachecontrol/__init__.py +29 -0
  2. lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py +70 -0
  5. lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py +161 -0
  6. lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py +75 -0
  7. lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py +500 -0
  8. lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py +119 -0
  9. lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py +157 -0
  10. lib/python3.12/site-packages/pip/_vendor/cachecontrol/py.typed +0 -0
  11. lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py +146 -0
  12. lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py +43 -0
  13. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc +0 -0
  19. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc +0 -0
  20. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc +0 -0
  21. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py +157 -0
  26. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc +0 -0
  29. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc +0 -0
  30. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc +0 -0
  31. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc +0 -0
  32. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc +0 -0
  33. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc +0 -0
  34. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py +23 -0
  41. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py +108 -0
  42. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py +170 -0
  43. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py +987 -0
  44. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py +685 -0
  45. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py +154 -0
  46. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py +518 -0
  47. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py +160 -0
  48. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py +83 -0
  49. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py +349 -0
  50. lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py +185 -0
lib/python3.12/site-packages/pip/_vendor/cachecontrol/__init__.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """CacheControl import Interface.
6
+
7
+ Make it easy to import from cachecontrol without long namespaces.
8
+ """
9
+
10
+ __author__ = "Eric Larson"
11
+ __email__ = "eric@ionrock.org"
12
+ __version__ = "0.14.1"
13
+
14
+ from pip._vendor.cachecontrol.adapter import CacheControlAdapter
15
+ from pip._vendor.cachecontrol.controller import CacheController
16
+ from pip._vendor.cachecontrol.wrapper import CacheControl
17
+
18
+ __all__ = [
19
+ "__author__",
20
+ "__email__",
21
+ "__version__",
22
+ "CacheControlAdapter",
23
+ "CacheController",
24
+ "CacheControl",
25
+ ]
26
+
27
+ import logging
28
+
29
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc ADDED
Binary file (16.2 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc ADDED
Binary file (6.7 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ from argparse import ArgumentParser
8
+ from typing import TYPE_CHECKING
9
+
10
+ from pip._vendor import requests
11
+
12
+ from pip._vendor.cachecontrol.adapter import CacheControlAdapter
13
+ from pip._vendor.cachecontrol.cache import DictCache
14
+ from pip._vendor.cachecontrol.controller import logger
15
+
16
+ if TYPE_CHECKING:
17
+ from argparse import Namespace
18
+
19
+ from pip._vendor.cachecontrol.controller import CacheController
20
+
21
+
22
+ def setup_logging() -> None:
23
+ logger.setLevel(logging.DEBUG)
24
+ handler = logging.StreamHandler()
25
+ logger.addHandler(handler)
26
+
27
+
28
+ def get_session() -> requests.Session:
29
+ adapter = CacheControlAdapter(
30
+ DictCache(), cache_etags=True, serializer=None, heuristic=None
31
+ )
32
+ sess = requests.Session()
33
+ sess.mount("http://", adapter)
34
+ sess.mount("https://", adapter)
35
+
36
+ sess.cache_controller = adapter.controller # type: ignore[attr-defined]
37
+ return sess
38
+
39
+
40
+ def get_args() -> Namespace:
41
+ parser = ArgumentParser()
42
+ parser.add_argument("url", help="The URL to try and cache")
43
+ return parser.parse_args()
44
+
45
+
46
+ def main() -> None:
47
+ args = get_args()
48
+ sess = get_session()
49
+
50
+ # Make a request to get a response
51
+ resp = sess.get(args.url)
52
+
53
+ # Turn on logging
54
+ setup_logging()
55
+
56
+ # try setting the cache
57
+ cache_controller: CacheController = (
58
+ sess.cache_controller # type: ignore[attr-defined]
59
+ )
60
+ cache_controller.cache_response(resp.request, resp.raw)
61
+
62
+ # Now try to get it
63
+ if cache_controller.cached_request(resp.request):
64
+ print("Cached!")
65
+ else:
66
+ print("Not cached :(")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ main()
lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ import functools
7
+ import types
8
+ import zlib
9
+ from typing import TYPE_CHECKING, Any, Collection, Mapping
10
+
11
+ from pip._vendor.requests.adapters import HTTPAdapter
12
+
13
+ from pip._vendor.cachecontrol.cache import DictCache
14
+ from pip._vendor.cachecontrol.controller import PERMANENT_REDIRECT_STATUSES, CacheController
15
+ from pip._vendor.cachecontrol.filewrapper import CallbackFileWrapper
16
+
17
+ if TYPE_CHECKING:
18
+ from pip._vendor.requests import PreparedRequest, Response
19
+ from pip._vendor.urllib3 import HTTPResponse
20
+
21
+ from pip._vendor.cachecontrol.cache import BaseCache
22
+ from pip._vendor.cachecontrol.heuristics import BaseHeuristic
23
+ from pip._vendor.cachecontrol.serialize import Serializer
24
+
25
+
26
+ class CacheControlAdapter(HTTPAdapter):
27
+ invalidating_methods = {"PUT", "PATCH", "DELETE"}
28
+
29
+ def __init__(
30
+ self,
31
+ cache: BaseCache | None = None,
32
+ cache_etags: bool = True,
33
+ controller_class: type[CacheController] | None = None,
34
+ serializer: Serializer | None = None,
35
+ heuristic: BaseHeuristic | None = None,
36
+ cacheable_methods: Collection[str] | None = None,
37
+ *args: Any,
38
+ **kw: Any,
39
+ ) -> None:
40
+ super().__init__(*args, **kw)
41
+ self.cache = DictCache() if cache is None else cache
42
+ self.heuristic = heuristic
43
+ self.cacheable_methods = cacheable_methods or ("GET",)
44
+
45
+ controller_factory = controller_class or CacheController
46
+ self.controller = controller_factory(
47
+ self.cache, cache_etags=cache_etags, serializer=serializer
48
+ )
49
+
50
+ def send(
51
+ self,
52
+ request: PreparedRequest,
53
+ stream: bool = False,
54
+ timeout: None | float | tuple[float, float] | tuple[float, None] = None,
55
+ verify: bool | str = True,
56
+ cert: (None | bytes | str | tuple[bytes | str, bytes | str]) = None,
57
+ proxies: Mapping[str, str] | None = None,
58
+ cacheable_methods: Collection[str] | None = None,
59
+ ) -> Response:
60
+ """
61
+ Send a request. Use the request information to see if it
62
+ exists in the cache and cache the response if we need to and can.
63
+ """
64
+ cacheable = cacheable_methods or self.cacheable_methods
65
+ if request.method in cacheable:
66
+ try:
67
+ cached_response = self.controller.cached_request(request)
68
+ except zlib.error:
69
+ cached_response = None
70
+ if cached_response:
71
+ return self.build_response(request, cached_response, from_cache=True)
72
+
73
+ # check for etags and add headers if appropriate
74
+ request.headers.update(self.controller.conditional_headers(request))
75
+
76
+ resp = super().send(request, stream, timeout, verify, cert, proxies)
77
+
78
+ return resp
79
+
80
+ def build_response( # type: ignore[override]
81
+ self,
82
+ request: PreparedRequest,
83
+ response: HTTPResponse,
84
+ from_cache: bool = False,
85
+ cacheable_methods: Collection[str] | None = None,
86
+ ) -> Response:
87
+ """
88
+ Build a response by making a request or using the cache.
89
+
90
+ This will end up calling send and returning a potentially
91
+ cached response
92
+ """
93
+ cacheable = cacheable_methods or self.cacheable_methods
94
+ if not from_cache and request.method in cacheable:
95
+ # Check for any heuristics that might update headers
96
+ # before trying to cache.
97
+ if self.heuristic:
98
+ response = self.heuristic.apply(response)
99
+
100
+ # apply any expiration heuristics
101
+ if response.status == 304:
102
+ # We must have sent an ETag request. This could mean
103
+ # that we've been expired already or that we simply
104
+ # have an etag. In either case, we want to try and
105
+ # update the cache if that is the case.
106
+ cached_response = self.controller.update_cached_response(
107
+ request, response
108
+ )
109
+
110
+ if cached_response is not response:
111
+ from_cache = True
112
+
113
+ # We are done with the server response, read a
114
+ # possible response body (compliant servers will
115
+ # not return one, but we cannot be 100% sure) and
116
+ # release the connection back to the pool.
117
+ response.read(decode_content=False)
118
+ response.release_conn()
119
+
120
+ response = cached_response
121
+
122
+ # We always cache the 301 responses
123
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
124
+ self.controller.cache_response(request, response)
125
+ else:
126
+ # Wrap the response file with a wrapper that will cache the
127
+ # response when the stream has been consumed.
128
+ response._fp = CallbackFileWrapper( # type: ignore[assignment]
129
+ response._fp, # type: ignore[arg-type]
130
+ functools.partial(
131
+ self.controller.cache_response, request, response
132
+ ),
133
+ )
134
+ if response.chunked:
135
+ super_update_chunk_length = response._update_chunk_length
136
+
137
+ def _update_chunk_length(self: HTTPResponse) -> None:
138
+ super_update_chunk_length()
139
+ if self.chunk_left == 0:
140
+ self._fp._close() # type: ignore[union-attr]
141
+
142
+ response._update_chunk_length = types.MethodType( # type: ignore[method-assign]
143
+ _update_chunk_length, response
144
+ )
145
+
146
+ resp: Response = super().build_response(request, response)
147
+
148
+ # See if we should invalidate the cache.
149
+ if request.method in self.invalidating_methods and resp.ok:
150
+ assert request.url is not None
151
+ cache_url = self.controller.cache_url(request.url)
152
+ self.cache.delete(cache_url)
153
+
154
+ # Give the request a from_cache attr to let people use it
155
+ resp.from_cache = from_cache # type: ignore[attr-defined]
156
+
157
+ return resp
158
+
159
+ def close(self) -> None:
160
+ self.cache.close()
161
+ super().close() # type: ignore[no-untyped-call]
lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ The cache object API for implementing caches. The default is a thread
7
+ safe in-memory dictionary.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from threading import Lock
13
+ from typing import IO, TYPE_CHECKING, MutableMapping
14
+
15
+ if TYPE_CHECKING:
16
+ from datetime import datetime
17
+
18
+
19
+ class BaseCache:
20
+ def get(self, key: str) -> bytes | None:
21
+ raise NotImplementedError()
22
+
23
+ def set(
24
+ self, key: str, value: bytes, expires: int | datetime | None = None
25
+ ) -> None:
26
+ raise NotImplementedError()
27
+
28
+ def delete(self, key: str) -> None:
29
+ raise NotImplementedError()
30
+
31
+ def close(self) -> None:
32
+ pass
33
+
34
+
35
+ class DictCache(BaseCache):
36
+ def __init__(self, init_dict: MutableMapping[str, bytes] | None = None) -> None:
37
+ self.lock = Lock()
38
+ self.data = init_dict or {}
39
+
40
+ def get(self, key: str) -> bytes | None:
41
+ return self.data.get(key, None)
42
+
43
+ def set(
44
+ self, key: str, value: bytes, expires: int | datetime | None = None
45
+ ) -> None:
46
+ with self.lock:
47
+ self.data.update({key: value})
48
+
49
+ def delete(self, key: str) -> None:
50
+ with self.lock:
51
+ if key in self.data:
52
+ self.data.pop(key)
53
+
54
+
55
+ class SeparateBodyBaseCache(BaseCache):
56
+ """
57
+ In this variant, the body is not stored mixed in with the metadata, but is
58
+ passed in (as a bytes-like object) in a separate call to ``set_body()``.
59
+
60
+ That is, the expected interaction pattern is::
61
+
62
+ cache.set(key, serialized_metadata)
63
+ cache.set_body(key)
64
+
65
+ Similarly, the body should be loaded separately via ``get_body()``.
66
+ """
67
+
68
+ def set_body(self, key: str, body: bytes) -> None:
69
+ raise NotImplementedError()
70
+
71
+ def get_body(self, key: str) -> IO[bytes] | None:
72
+ """
73
+ Return the body as file-like object.
74
+ """
75
+ raise NotImplementedError()
lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ The httplib2 algorithms ported for use with requests.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import calendar
12
+ import logging
13
+ import re
14
+ import time
15
+ from email.utils import parsedate_tz
16
+ from typing import TYPE_CHECKING, Collection, Mapping
17
+
18
+ from pip._vendor.requests.structures import CaseInsensitiveDict
19
+
20
+ from pip._vendor.cachecontrol.cache import DictCache, SeparateBodyBaseCache
21
+ from pip._vendor.cachecontrol.serialize import Serializer
22
+
23
+ if TYPE_CHECKING:
24
+ from typing import Literal
25
+
26
+ from pip._vendor.requests import PreparedRequest
27
+ from pip._vendor.urllib3 import HTTPResponse
28
+
29
+ from pip._vendor.cachecontrol.cache import BaseCache
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
34
+
35
+ PERMANENT_REDIRECT_STATUSES = (301, 308)
36
+
37
+
38
+ def parse_uri(uri: str) -> tuple[str, str, str, str, str]:
39
+ """Parses a URI using the regex given in Appendix B of RFC 3986.
40
+
41
+ (scheme, authority, path, query, fragment) = parse_uri(uri)
42
+ """
43
+ match = URI.match(uri)
44
+ assert match is not None
45
+ groups = match.groups()
46
+ return (groups[1], groups[3], groups[4], groups[6], groups[8])
47
+
48
+
49
+ class CacheController:
50
+ """An interface to see if request should cached or not."""
51
+
52
+ def __init__(
53
+ self,
54
+ cache: BaseCache | None = None,
55
+ cache_etags: bool = True,
56
+ serializer: Serializer | None = None,
57
+ status_codes: Collection[int] | None = None,
58
+ ):
59
+ self.cache = DictCache() if cache is None else cache
60
+ self.cache_etags = cache_etags
61
+ self.serializer = serializer or Serializer()
62
+ self.cacheable_status_codes = status_codes or (200, 203, 300, 301, 308)
63
+
64
+ @classmethod
65
+ def _urlnorm(cls, uri: str) -> str:
66
+ """Normalize the URL to create a safe key for the cache"""
67
+ (scheme, authority, path, query, fragment) = parse_uri(uri)
68
+ if not scheme or not authority:
69
+ raise Exception("Only absolute URIs are allowed. uri = %s" % uri)
70
+
71
+ scheme = scheme.lower()
72
+ authority = authority.lower()
73
+
74
+ if not path:
75
+ path = "/"
76
+
77
+ # Could do syntax based normalization of the URI before
78
+ # computing the digest. See Section 6.2.2 of Std 66.
79
+ request_uri = query and "?".join([path, query]) or path
80
+ defrag_uri = scheme + "://" + authority + request_uri
81
+
82
+ return defrag_uri
83
+
84
+ @classmethod
85
+ def cache_url(cls, uri: str) -> str:
86
+ return cls._urlnorm(uri)
87
+
88
+ def parse_cache_control(self, headers: Mapping[str, str]) -> dict[str, int | None]:
89
+ known_directives = {
90
+ # https://tools.ietf.org/html/rfc7234#section-5.2
91
+ "max-age": (int, True),
92
+ "max-stale": (int, False),
93
+ "min-fresh": (int, True),
94
+ "no-cache": (None, False),
95
+ "no-store": (None, False),
96
+ "no-transform": (None, False),
97
+ "only-if-cached": (None, False),
98
+ "must-revalidate": (None, False),
99
+ "public": (None, False),
100
+ "private": (None, False),
101
+ "proxy-revalidate": (None, False),
102
+ "s-maxage": (int, True),
103
+ }
104
+
105
+ cc_headers = headers.get("cache-control", headers.get("Cache-Control", ""))
106
+
107
+ retval: dict[str, int | None] = {}
108
+
109
+ for cc_directive in cc_headers.split(","):
110
+ if not cc_directive.strip():
111
+ continue
112
+
113
+ parts = cc_directive.split("=", 1)
114
+ directive = parts[0].strip()
115
+
116
+ try:
117
+ typ, required = known_directives[directive]
118
+ except KeyError:
119
+ logger.debug("Ignoring unknown cache-control directive: %s", directive)
120
+ continue
121
+
122
+ if not typ or not required:
123
+ retval[directive] = None
124
+ if typ:
125
+ try:
126
+ retval[directive] = typ(parts[1].strip())
127
+ except IndexError:
128
+ if required:
129
+ logger.debug(
130
+ "Missing value for cache-control " "directive: %s",
131
+ directive,
132
+ )
133
+ except ValueError:
134
+ logger.debug(
135
+ "Invalid value for cache-control directive " "%s, must be %s",
136
+ directive,
137
+ typ.__name__,
138
+ )
139
+
140
+ return retval
141
+
142
+ def _load_from_cache(self, request: PreparedRequest) -> HTTPResponse | None:
143
+ """
144
+ Load a cached response, or return None if it's not available.
145
+ """
146
+ # We do not support caching of partial content: so if the request contains a
147
+ # Range header then we don't want to load anything from the cache.
148
+ if "Range" in request.headers:
149
+ return None
150
+
151
+ cache_url = request.url
152
+ assert cache_url is not None
153
+ cache_data = self.cache.get(cache_url)
154
+ if cache_data is None:
155
+ logger.debug("No cache entry available")
156
+ return None
157
+
158
+ if isinstance(self.cache, SeparateBodyBaseCache):
159
+ body_file = self.cache.get_body(cache_url)
160
+ else:
161
+ body_file = None
162
+
163
+ result = self.serializer.loads(request, cache_data, body_file)
164
+ if result is None:
165
+ logger.warning("Cache entry deserialization failed, entry ignored")
166
+ return result
167
+
168
+ def cached_request(self, request: PreparedRequest) -> HTTPResponse | Literal[False]:
169
+ """
170
+ Return a cached response if it exists in the cache, otherwise
171
+ return False.
172
+ """
173
+ assert request.url is not None
174
+ cache_url = self.cache_url(request.url)
175
+ logger.debug('Looking up "%s" in the cache', cache_url)
176
+ cc = self.parse_cache_control(request.headers)
177
+
178
+ # Bail out if the request insists on fresh data
179
+ if "no-cache" in cc:
180
+ logger.debug('Request header has "no-cache", cache bypassed')
181
+ return False
182
+
183
+ if "max-age" in cc and cc["max-age"] == 0:
184
+ logger.debug('Request header has "max_age" as 0, cache bypassed')
185
+ return False
186
+
187
+ # Check whether we can load the response from the cache:
188
+ resp = self._load_from_cache(request)
189
+ if not resp:
190
+ return False
191
+
192
+ # If we have a cached permanent redirect, return it immediately. We
193
+ # don't need to test our response for other headers b/c it is
194
+ # intrinsically "cacheable" as it is Permanent.
195
+ #
196
+ # See:
197
+ # https://tools.ietf.org/html/rfc7231#section-6.4.2
198
+ #
199
+ # Client can try to refresh the value by repeating the request
200
+ # with cache busting headers as usual (ie no-cache).
201
+ if int(resp.status) in PERMANENT_REDIRECT_STATUSES:
202
+ msg = (
203
+ "Returning cached permanent redirect response "
204
+ "(ignoring date and etag information)"
205
+ )
206
+ logger.debug(msg)
207
+ return resp
208
+
209
+ headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(resp.headers)
210
+ if not headers or "date" not in headers:
211
+ if "etag" not in headers:
212
+ # Without date or etag, the cached response can never be used
213
+ # and should be deleted.
214
+ logger.debug("Purging cached response: no date or etag")
215
+ self.cache.delete(cache_url)
216
+ logger.debug("Ignoring cached response: no date")
217
+ return False
218
+
219
+ now = time.time()
220
+ time_tuple = parsedate_tz(headers["date"])
221
+ assert time_tuple is not None
222
+ date = calendar.timegm(time_tuple[:6])
223
+ current_age = max(0, now - date)
224
+ logger.debug("Current age based on date: %i", current_age)
225
+
226
+ # TODO: There is an assumption that the result will be a
227
+ # urllib3 response object. This may not be best since we
228
+ # could probably avoid instantiating or constructing the
229
+ # response until we know we need it.
230
+ resp_cc = self.parse_cache_control(headers)
231
+
232
+ # determine freshness
233
+ freshness_lifetime = 0
234
+
235
+ # Check the max-age pragma in the cache control header
236
+ max_age = resp_cc.get("max-age")
237
+ if max_age is not None:
238
+ freshness_lifetime = max_age
239
+ logger.debug("Freshness lifetime from max-age: %i", freshness_lifetime)
240
+
241
+ # If there isn't a max-age, check for an expires header
242
+ elif "expires" in headers:
243
+ expires = parsedate_tz(headers["expires"])
244
+ if expires is not None:
245
+ expire_time = calendar.timegm(expires[:6]) - date
246
+ freshness_lifetime = max(0, expire_time)
247
+ logger.debug("Freshness lifetime from expires: %i", freshness_lifetime)
248
+
249
+ # Determine if we are setting freshness limit in the
250
+ # request. Note, this overrides what was in the response.
251
+ max_age = cc.get("max-age")
252
+ if max_age is not None:
253
+ freshness_lifetime = max_age
254
+ logger.debug(
255
+ "Freshness lifetime from request max-age: %i", freshness_lifetime
256
+ )
257
+
258
+ min_fresh = cc.get("min-fresh")
259
+ if min_fresh is not None:
260
+ # adjust our current age by our min fresh
261
+ current_age += min_fresh
262
+ logger.debug("Adjusted current age from min-fresh: %i", current_age)
263
+
264
+ # Return entry if it is fresh enough
265
+ if freshness_lifetime > current_age:
266
+ logger.debug('The response is "fresh", returning cached response')
267
+ logger.debug("%i > %i", freshness_lifetime, current_age)
268
+ return resp
269
+
270
+ # we're not fresh. If we don't have an Etag, clear it out
271
+ if "etag" not in headers:
272
+ logger.debug('The cached response is "stale" with no etag, purging')
273
+ self.cache.delete(cache_url)
274
+
275
+ # return the original handler
276
+ return False
277
+
278
+ def conditional_headers(self, request: PreparedRequest) -> dict[str, str]:
279
+ resp = self._load_from_cache(request)
280
+ new_headers = {}
281
+
282
+ if resp:
283
+ headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(resp.headers)
284
+
285
+ if "etag" in headers:
286
+ new_headers["If-None-Match"] = headers["ETag"]
287
+
288
+ if "last-modified" in headers:
289
+ new_headers["If-Modified-Since"] = headers["Last-Modified"]
290
+
291
+ return new_headers
292
+
293
+ def _cache_set(
294
+ self,
295
+ cache_url: str,
296
+ request: PreparedRequest,
297
+ response: HTTPResponse,
298
+ body: bytes | None = None,
299
+ expires_time: int | None = None,
300
+ ) -> None:
301
+ """
302
+ Store the data in the cache.
303
+ """
304
+ if isinstance(self.cache, SeparateBodyBaseCache):
305
+ # We pass in the body separately; just put a placeholder empty
306
+ # string in the metadata.
307
+ self.cache.set(
308
+ cache_url,
309
+ self.serializer.dumps(request, response, b""),
310
+ expires=expires_time,
311
+ )
312
+ # body is None can happen when, for example, we're only updating
313
+ # headers, as is the case in update_cached_response().
314
+ if body is not None:
315
+ self.cache.set_body(cache_url, body)
316
+ else:
317
+ self.cache.set(
318
+ cache_url,
319
+ self.serializer.dumps(request, response, body),
320
+ expires=expires_time,
321
+ )
322
+
323
+ def cache_response(
324
+ self,
325
+ request: PreparedRequest,
326
+ response: HTTPResponse,
327
+ body: bytes | None = None,
328
+ status_codes: Collection[int] | None = None,
329
+ ) -> None:
330
+ """
331
+ Algorithm for caching requests.
332
+
333
+ This assumes a requests Response object.
334
+ """
335
+ # From httplib2: Don't cache 206's since we aren't going to
336
+ # handle byte range requests
337
+ cacheable_status_codes = status_codes or self.cacheable_status_codes
338
+ if response.status not in cacheable_status_codes:
339
+ logger.debug(
340
+ "Status code %s not in %s", response.status, cacheable_status_codes
341
+ )
342
+ return
343
+
344
+ response_headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(
345
+ response.headers
346
+ )
347
+
348
+ if "date" in response_headers:
349
+ time_tuple = parsedate_tz(response_headers["date"])
350
+ assert time_tuple is not None
351
+ date = calendar.timegm(time_tuple[:6])
352
+ else:
353
+ date = 0
354
+
355
+ # If we've been given a body, our response has a Content-Length, that
356
+ # Content-Length is valid then we can check to see if the body we've
357
+ # been given matches the expected size, and if it doesn't we'll just
358
+ # skip trying to cache it.
359
+ if (
360
+ body is not None
361
+ and "content-length" in response_headers
362
+ and response_headers["content-length"].isdigit()
363
+ and int(response_headers["content-length"]) != len(body)
364
+ ):
365
+ return
366
+
367
+ cc_req = self.parse_cache_control(request.headers)
368
+ cc = self.parse_cache_control(response_headers)
369
+
370
+ assert request.url is not None
371
+ cache_url = self.cache_url(request.url)
372
+ logger.debug('Updating cache with response from "%s"', cache_url)
373
+
374
+ # Delete it from the cache if we happen to have it stored there
375
+ no_store = False
376
+ if "no-store" in cc:
377
+ no_store = True
378
+ logger.debug('Response header has "no-store"')
379
+ if "no-store" in cc_req:
380
+ no_store = True
381
+ logger.debug('Request header has "no-store"')
382
+ if no_store and self.cache.get(cache_url):
383
+ logger.debug('Purging existing cache entry to honor "no-store"')
384
+ self.cache.delete(cache_url)
385
+ if no_store:
386
+ return
387
+
388
+ # https://tools.ietf.org/html/rfc7234#section-4.1:
389
+ # A Vary header field-value of "*" always fails to match.
390
+ # Storing such a response leads to a deserialization warning
391
+ # during cache lookup and is not allowed to ever be served,
392
+ # so storing it can be avoided.
393
+ if "*" in response_headers.get("vary", ""):
394
+ logger.debug('Response header has "Vary: *"')
395
+ return
396
+
397
+ # If we've been given an etag, then keep the response
398
+ if self.cache_etags and "etag" in response_headers:
399
+ expires_time = 0
400
+ if response_headers.get("expires"):
401
+ expires = parsedate_tz(response_headers["expires"])
402
+ if expires is not None:
403
+ expires_time = calendar.timegm(expires[:6]) - date
404
+
405
+ expires_time = max(expires_time, 14 * 86400)
406
+
407
+ logger.debug(f"etag object cached for {expires_time} seconds")
408
+ logger.debug("Caching due to etag")
409
+ self._cache_set(cache_url, request, response, body, expires_time)
410
+
411
+ # Add to the cache any permanent redirects. We do this before looking
412
+ # that the Date headers.
413
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
414
+ logger.debug("Caching permanent redirect")
415
+ self._cache_set(cache_url, request, response, b"")
416
+
417
+ # Add to the cache if the response headers demand it. If there
418
+ # is no date header then we can't do anything about expiring
419
+ # the cache.
420
+ elif "date" in response_headers:
421
+ time_tuple = parsedate_tz(response_headers["date"])
422
+ assert time_tuple is not None
423
+ date = calendar.timegm(time_tuple[:6])
424
+ # cache when there is a max-age > 0
425
+ max_age = cc.get("max-age")
426
+ if max_age is not None and max_age > 0:
427
+ logger.debug("Caching b/c date exists and max-age > 0")
428
+ expires_time = max_age
429
+ self._cache_set(
430
+ cache_url,
431
+ request,
432
+ response,
433
+ body,
434
+ expires_time,
435
+ )
436
+
437
+ # If the request can expire, it means we should cache it
438
+ # in the meantime.
439
+ elif "expires" in response_headers:
440
+ if response_headers["expires"]:
441
+ expires = parsedate_tz(response_headers["expires"])
442
+ if expires is not None:
443
+ expires_time = calendar.timegm(expires[:6]) - date
444
+ else:
445
+ expires_time = None
446
+
447
+ logger.debug(
448
+ "Caching b/c of expires header. expires in {} seconds".format(
449
+ expires_time
450
+ )
451
+ )
452
+ self._cache_set(
453
+ cache_url,
454
+ request,
455
+ response,
456
+ body,
457
+ expires_time,
458
+ )
459
+
460
+ def update_cached_response(
461
+ self, request: PreparedRequest, response: HTTPResponse
462
+ ) -> HTTPResponse:
463
+ """On a 304 we will get a new set of headers that we want to
464
+ update our cached value with, assuming we have one.
465
+
466
+ This should only ever be called when we've sent an ETag and
467
+ gotten a 304 as the response.
468
+ """
469
+ assert request.url is not None
470
+ cache_url = self.cache_url(request.url)
471
+ cached_response = self._load_from_cache(request)
472
+
473
+ if not cached_response:
474
+ # we didn't have a cached response
475
+ return response
476
+
477
+ # Lets update our headers with the headers from the new request:
478
+ # http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-26#section-4.1
479
+ #
480
+ # The server isn't supposed to send headers that would make
481
+ # the cached body invalid. But... just in case, we'll be sure
482
+ # to strip out ones we know that might be problmatic due to
483
+ # typical assumptions.
484
+ excluded_headers = ["content-length"]
485
+
486
+ cached_response.headers.update(
487
+ {
488
+ k: v
489
+ for k, v in response.headers.items()
490
+ if k.lower() not in excluded_headers
491
+ }
492
+ )
493
+
494
+ # we want a 200 b/c we have content via the cache
495
+ cached_response.status = 200
496
+
497
+ # update our cache
498
+ self._cache_set(cache_url, request, cached_response)
499
+
500
+ return cached_response
lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ import mmap
7
+ from tempfile import NamedTemporaryFile
8
+ from typing import TYPE_CHECKING, Any, Callable
9
+
10
+ if TYPE_CHECKING:
11
+ from http.client import HTTPResponse
12
+
13
+
14
+ class CallbackFileWrapper:
15
+ """
16
+ Small wrapper around a fp object which will tee everything read into a
17
+ buffer, and when that file is closed it will execute a callback with the
18
+ contents of that buffer.
19
+
20
+ All attributes are proxied to the underlying file object.
21
+
22
+ This class uses members with a double underscore (__) leading prefix so as
23
+ not to accidentally shadow an attribute.
24
+
25
+ The data is stored in a temporary file until it is all available. As long
26
+ as the temporary files directory is disk-based (sometimes it's a
27
+ memory-backed-``tmpfs`` on Linux), data will be unloaded to disk if memory
28
+ pressure is high. For small files the disk usually won't be used at all,
29
+ it'll all be in the filesystem memory cache, so there should be no
30
+ performance impact.
31
+ """
32
+
33
+ def __init__(
34
+ self, fp: HTTPResponse, callback: Callable[[bytes], None] | None
35
+ ) -> None:
36
+ self.__buf = NamedTemporaryFile("rb+", delete=True)
37
+ self.__fp = fp
38
+ self.__callback = callback
39
+
40
+ def __getattr__(self, name: str) -> Any:
41
+ # The vagaries of garbage collection means that self.__fp is
42
+ # not always set. By using __getattribute__ and the private
43
+ # name[0] allows looking up the attribute value and raising an
44
+ # AttributeError when it doesn't exist. This stop things from
45
+ # infinitely recursing calls to getattr in the case where
46
+ # self.__fp hasn't been set.
47
+ #
48
+ # [0] https://docs.python.org/2/reference/expressions.html#atom-identifiers
49
+ fp = self.__getattribute__("_CallbackFileWrapper__fp")
50
+ return getattr(fp, name)
51
+
52
+ def __is_fp_closed(self) -> bool:
53
+ try:
54
+ return self.__fp.fp is None
55
+
56
+ except AttributeError:
57
+ pass
58
+
59
+ try:
60
+ closed: bool = self.__fp.closed
61
+ return closed
62
+
63
+ except AttributeError:
64
+ pass
65
+
66
+ # We just don't cache it then.
67
+ # TODO: Add some logging here...
68
+ return False
69
+
70
+ def _close(self) -> None:
71
+ if self.__callback:
72
+ if self.__buf.tell() == 0:
73
+ # Empty file:
74
+ result = b""
75
+ else:
76
+ # Return the data without actually loading it into memory,
77
+ # relying on Python's buffer API and mmap(). mmap() just gives
78
+ # a view directly into the filesystem's memory cache, so it
79
+ # doesn't result in duplicate memory use.
80
+ self.__buf.seek(0, 0)
81
+ result = memoryview(
82
+ mmap.mmap(self.__buf.fileno(), 0, access=mmap.ACCESS_READ)
83
+ )
84
+ self.__callback(result)
85
+
86
+ # We assign this to None here, because otherwise we can get into
87
+ # really tricky problems where the CPython interpreter dead locks
88
+ # because the callback is holding a reference to something which
89
+ # has a __del__ method. Setting this to None breaks the cycle
90
+ # and allows the garbage collector to do it's thing normally.
91
+ self.__callback = None
92
+
93
+ # Closing the temporary file releases memory and frees disk space.
94
+ # Important when caching big files.
95
+ self.__buf.close()
96
+
97
+ def read(self, amt: int | None = None) -> bytes:
98
+ data: bytes = self.__fp.read(amt)
99
+ if data:
100
+ # We may be dealing with b'', a sign that things are over:
101
+ # it's passed e.g. after we've already closed self.__buf.
102
+ self.__buf.write(data)
103
+ if self.__is_fp_closed():
104
+ self._close()
105
+
106
+ return data
107
+
108
+ def _safe_read(self, amt: int) -> bytes:
109
+ data: bytes = self.__fp._safe_read(amt) # type: ignore[attr-defined]
110
+ if amt == 2 and data == b"\r\n":
111
+ # urllib executes this read to toss the CRLF at the end
112
+ # of the chunk.
113
+ return data
114
+
115
+ self.__buf.write(data)
116
+ if self.__is_fp_closed():
117
+ self._close()
118
+
119
+ return data
lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ import calendar
7
+ import time
8
+ from datetime import datetime, timedelta, timezone
9
+ from email.utils import formatdate, parsedate, parsedate_tz
10
+ from typing import TYPE_CHECKING, Any, Mapping
11
+
12
+ if TYPE_CHECKING:
13
+ from pip._vendor.urllib3 import HTTPResponse
14
+
15
+ TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT"
16
+
17
+
18
+ def expire_after(delta: timedelta, date: datetime | None = None) -> datetime:
19
+ date = date or datetime.now(timezone.utc)
20
+ return date + delta
21
+
22
+
23
+ def datetime_to_header(dt: datetime) -> str:
24
+ return formatdate(calendar.timegm(dt.timetuple()))
25
+
26
+
27
+ class BaseHeuristic:
28
+ def warning(self, response: HTTPResponse) -> str | None:
29
+ """
30
+ Return a valid 1xx warning header value describing the cache
31
+ adjustments.
32
+
33
+ The response is provided too allow warnings like 113
34
+ http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need
35
+ to explicitly say response is over 24 hours old.
36
+ """
37
+ return '110 - "Response is Stale"'
38
+
39
+ def update_headers(self, response: HTTPResponse) -> dict[str, str]:
40
+ """Update the response headers with any new headers.
41
+
42
+ NOTE: This SHOULD always include some Warning header to
43
+ signify that the response was cached by the client, not
44
+ by way of the provided headers.
45
+ """
46
+ return {}
47
+
48
+ def apply(self, response: HTTPResponse) -> HTTPResponse:
49
+ updated_headers = self.update_headers(response)
50
+
51
+ if updated_headers:
52
+ response.headers.update(updated_headers)
53
+ warning_header_value = self.warning(response)
54
+ if warning_header_value is not None:
55
+ response.headers.update({"Warning": warning_header_value})
56
+
57
+ return response
58
+
59
+
60
+ class OneDayCache(BaseHeuristic):
61
+ """
62
+ Cache the response by providing an expires 1 day in the
63
+ future.
64
+ """
65
+
66
+ def update_headers(self, response: HTTPResponse) -> dict[str, str]:
67
+ headers = {}
68
+
69
+ if "expires" not in response.headers:
70
+ date = parsedate(response.headers["date"])
71
+ expires = expire_after(
72
+ timedelta(days=1),
73
+ date=datetime(*date[:6], tzinfo=timezone.utc), # type: ignore[index,misc]
74
+ )
75
+ headers["expires"] = datetime_to_header(expires)
76
+ headers["cache-control"] = "public"
77
+ return headers
78
+
79
+
80
+ class ExpiresAfter(BaseHeuristic):
81
+ """
82
+ Cache **all** requests for a defined time period.
83
+ """
84
+
85
+ def __init__(self, **kw: Any) -> None:
86
+ self.delta = timedelta(**kw)
87
+
88
+ def update_headers(self, response: HTTPResponse) -> dict[str, str]:
89
+ expires = expire_after(self.delta)
90
+ return {"expires": datetime_to_header(expires), "cache-control": "public"}
91
+
92
+ def warning(self, response: HTTPResponse) -> str | None:
93
+ tmpl = "110 - Automatically cached for %s. Response might be stale"
94
+ return tmpl % self.delta
95
+
96
+
97
+ class LastModified(BaseHeuristic):
98
+ """
99
+ If there is no Expires header already, fall back on Last-Modified
100
+ using the heuristic from
101
+ http://tools.ietf.org/html/rfc7234#section-4.2.2
102
+ to calculate a reasonable value.
103
+
104
+ Firefox also does something like this per
105
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
106
+ http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397
107
+ Unlike mozilla we limit this to 24-hr.
108
+ """
109
+
110
+ cacheable_by_default_statuses = {
111
+ 200,
112
+ 203,
113
+ 204,
114
+ 206,
115
+ 300,
116
+ 301,
117
+ 404,
118
+ 405,
119
+ 410,
120
+ 414,
121
+ 501,
122
+ }
123
+
124
+ def update_headers(self, resp: HTTPResponse) -> dict[str, str]:
125
+ headers: Mapping[str, str] = resp.headers
126
+
127
+ if "expires" in headers:
128
+ return {}
129
+
130
+ if "cache-control" in headers and headers["cache-control"] != "public":
131
+ return {}
132
+
133
+ if resp.status not in self.cacheable_by_default_statuses:
134
+ return {}
135
+
136
+ if "date" not in headers or "last-modified" not in headers:
137
+ return {}
138
+
139
+ time_tuple = parsedate_tz(headers["date"])
140
+ assert time_tuple is not None
141
+ date = calendar.timegm(time_tuple[:6])
142
+ last_modified = parsedate(headers["last-modified"])
143
+ if last_modified is None:
144
+ return {}
145
+
146
+ now = time.time()
147
+ current_age = max(0, now - date)
148
+ delta = date - calendar.timegm(last_modified)
149
+ freshness_lifetime = max(0, min(delta / 10, 24 * 3600))
150
+ if freshness_lifetime <= current_age:
151
+ return {}
152
+
153
+ expires = date + freshness_lifetime
154
+ return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))}
155
+
156
+ def warning(self, resp: HTTPResponse) -> str | None:
157
+ return None
lib/python3.12/site-packages/pip/_vendor/cachecontrol/py.typed ADDED
File without changes
lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ import io
7
+ from typing import IO, TYPE_CHECKING, Any, Mapping, cast
8
+
9
+ from pip._vendor import msgpack
10
+ from pip._vendor.requests.structures import CaseInsensitiveDict
11
+ from pip._vendor.urllib3 import HTTPResponse
12
+
13
+ if TYPE_CHECKING:
14
+ from pip._vendor.requests import PreparedRequest
15
+
16
+
17
+ class Serializer:
18
+ serde_version = "4"
19
+
20
+ def dumps(
21
+ self,
22
+ request: PreparedRequest,
23
+ response: HTTPResponse,
24
+ body: bytes | None = None,
25
+ ) -> bytes:
26
+ response_headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(
27
+ response.headers
28
+ )
29
+
30
+ if body is None:
31
+ # When a body isn't passed in, we'll read the response. We
32
+ # also update the response with a new file handler to be
33
+ # sure it acts as though it was never read.
34
+ body = response.read(decode_content=False)
35
+ response._fp = io.BytesIO(body) # type: ignore[assignment]
36
+ response.length_remaining = len(body)
37
+
38
+ data = {
39
+ "response": {
40
+ "body": body, # Empty bytestring if body is stored separately
41
+ "headers": {str(k): str(v) for k, v in response.headers.items()},
42
+ "status": response.status,
43
+ "version": response.version,
44
+ "reason": str(response.reason),
45
+ "decode_content": response.decode_content,
46
+ }
47
+ }
48
+
49
+ # Construct our vary headers
50
+ data["vary"] = {}
51
+ if "vary" in response_headers:
52
+ varied_headers = response_headers["vary"].split(",")
53
+ for header in varied_headers:
54
+ header = str(header).strip()
55
+ header_value = request.headers.get(header, None)
56
+ if header_value is not None:
57
+ header_value = str(header_value)
58
+ data["vary"][header] = header_value
59
+
60
+ return b",".join([f"cc={self.serde_version}".encode(), self.serialize(data)])
61
+
62
+ def serialize(self, data: dict[str, Any]) -> bytes:
63
+ return cast(bytes, msgpack.dumps(data, use_bin_type=True))
64
+
65
+ def loads(
66
+ self,
67
+ request: PreparedRequest,
68
+ data: bytes,
69
+ body_file: IO[bytes] | None = None,
70
+ ) -> HTTPResponse | None:
71
+ # Short circuit if we've been given an empty set of data
72
+ if not data:
73
+ return None
74
+
75
+ # Previous versions of this library supported other serialization
76
+ # formats, but these have all been removed.
77
+ if not data.startswith(f"cc={self.serde_version},".encode()):
78
+ return None
79
+
80
+ data = data[5:]
81
+ return self._loads_v4(request, data, body_file)
82
+
83
+ def prepare_response(
84
+ self,
85
+ request: PreparedRequest,
86
+ cached: Mapping[str, Any],
87
+ body_file: IO[bytes] | None = None,
88
+ ) -> HTTPResponse | None:
89
+ """Verify our vary headers match and construct a real urllib3
90
+ HTTPResponse object.
91
+ """
92
+ # Special case the '*' Vary value as it means we cannot actually
93
+ # determine if the cached response is suitable for this request.
94
+ # This case is also handled in the controller code when creating
95
+ # a cache entry, but is left here for backwards compatibility.
96
+ if "*" in cached.get("vary", {}):
97
+ return None
98
+
99
+ # Ensure that the Vary headers for the cached response match our
100
+ # request
101
+ for header, value in cached.get("vary", {}).items():
102
+ if request.headers.get(header, None) != value:
103
+ return None
104
+
105
+ body_raw = cached["response"].pop("body")
106
+
107
+ headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(
108
+ data=cached["response"]["headers"]
109
+ )
110
+ if headers.get("transfer-encoding", "") == "chunked":
111
+ headers.pop("transfer-encoding")
112
+
113
+ cached["response"]["headers"] = headers
114
+
115
+ try:
116
+ body: IO[bytes]
117
+ if body_file is None:
118
+ body = io.BytesIO(body_raw)
119
+ else:
120
+ body = body_file
121
+ except TypeError:
122
+ # This can happen if cachecontrol serialized to v1 format (pickle)
123
+ # using Python 2. A Python 2 str(byte string) will be unpickled as
124
+ # a Python 3 str (unicode string), which will cause the above to
125
+ # fail with:
126
+ #
127
+ # TypeError: 'str' does not support the buffer interface
128
+ body = io.BytesIO(body_raw.encode("utf8"))
129
+
130
+ # Discard any `strict` parameter serialized by older version of cachecontrol.
131
+ cached["response"].pop("strict", None)
132
+
133
+ return HTTPResponse(body=body, preload_content=False, **cached["response"])
134
+
135
+ def _loads_v4(
136
+ self,
137
+ request: PreparedRequest,
138
+ data: bytes,
139
+ body_file: IO[bytes] | None = None,
140
+ ) -> HTTPResponse | None:
141
+ try:
142
+ cached = msgpack.loads(data, raw=False)
143
+ except ValueError:
144
+ return None
145
+
146
+ return self.prepare_response(request, cached, body_file)
lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: 2015 Eric Larson
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ from __future__ import annotations
5
+
6
+ from typing import TYPE_CHECKING, Collection
7
+
8
+ from pip._vendor.cachecontrol.adapter import CacheControlAdapter
9
+ from pip._vendor.cachecontrol.cache import DictCache
10
+
11
+ if TYPE_CHECKING:
12
+ from pip._vendor import requests
13
+
14
+ from pip._vendor.cachecontrol.cache import BaseCache
15
+ from pip._vendor.cachecontrol.controller import CacheController
16
+ from pip._vendor.cachecontrol.heuristics import BaseHeuristic
17
+ from pip._vendor.cachecontrol.serialize import Serializer
18
+
19
+
20
+ def CacheControl(
21
+ sess: requests.Session,
22
+ cache: BaseCache | None = None,
23
+ cache_etags: bool = True,
24
+ serializer: Serializer | None = None,
25
+ heuristic: BaseHeuristic | None = None,
26
+ controller_class: type[CacheController] | None = None,
27
+ adapter_class: type[CacheControlAdapter] | None = None,
28
+ cacheable_methods: Collection[str] | None = None,
29
+ ) -> requests.Session:
30
+ cache = DictCache() if cache is None else cache
31
+ adapter_class = adapter_class or CacheControlAdapter
32
+ adapter = adapter_class(
33
+ cache,
34
+ cache_etags=cache_etags,
35
+ serializer=serializer,
36
+ heuristic=heuristic,
37
+ controller_class=controller_class,
38
+ cacheable_methods=cacheable_methods,
39
+ )
40
+ sess.mount("http://", adapter)
41
+ sess.mount("https://", adapter)
42
+
43
+ return sess
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (3.5 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc ADDED
Binary file (742 Bytes). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc ADDED
Binary file (26.6 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc ADDED
Binary file (2.64 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc ADDED
Binary file (3.23 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc ADDED
Binary file (4.73 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc ADDED
Binary file (2.62 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc ADDED
Binary file (4.09 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc ADDED
Binary file (4.76 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc ADDED
Binary file (12.1 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc ADDED
Binary file (8.2 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc ADDED
Binary file (14.1 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters
3
+ ~~~~~~~~~~~~~~~~~~~
4
+
5
+ Pygments formatters.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import re
12
+ import sys
13
+ import types
14
+ import fnmatch
15
+ from os.path import basename
16
+
17
+ from pip._vendor.pygments.formatters._mapping import FORMATTERS
18
+ from pip._vendor.pygments.plugin import find_plugin_formatters
19
+ from pip._vendor.pygments.util import ClassNotFound
20
+
21
+ __all__ = ['get_formatter_by_name', 'get_formatter_for_filename',
22
+ 'get_all_formatters', 'load_formatter_from_file'] + list(FORMATTERS)
23
+
24
+ _formatter_cache = {} # classes by name
25
+ _pattern_cache = {}
26
+
27
+
28
+ def _fn_matches(fn, glob):
29
+ """Return whether the supplied file name fn matches pattern filename."""
30
+ if glob not in _pattern_cache:
31
+ pattern = _pattern_cache[glob] = re.compile(fnmatch.translate(glob))
32
+ return pattern.match(fn)
33
+ return _pattern_cache[glob].match(fn)
34
+
35
+
36
+ def _load_formatters(module_name):
37
+ """Load a formatter (and all others in the module too)."""
38
+ mod = __import__(module_name, None, None, ['__all__'])
39
+ for formatter_name in mod.__all__:
40
+ cls = getattr(mod, formatter_name)
41
+ _formatter_cache[cls.name] = cls
42
+
43
+
44
+ def get_all_formatters():
45
+ """Return a generator for all formatter classes."""
46
+ # NB: this returns formatter classes, not info like get_all_lexers().
47
+ for info in FORMATTERS.values():
48
+ if info[1] not in _formatter_cache:
49
+ _load_formatters(info[0])
50
+ yield _formatter_cache[info[1]]
51
+ for _, formatter in find_plugin_formatters():
52
+ yield formatter
53
+
54
+
55
+ def find_formatter_class(alias):
56
+ """Lookup a formatter by alias.
57
+
58
+ Returns None if not found.
59
+ """
60
+ for module_name, name, aliases, _, _ in FORMATTERS.values():
61
+ if alias in aliases:
62
+ if name not in _formatter_cache:
63
+ _load_formatters(module_name)
64
+ return _formatter_cache[name]
65
+ for _, cls in find_plugin_formatters():
66
+ if alias in cls.aliases:
67
+ return cls
68
+
69
+
70
+ def get_formatter_by_name(_alias, **options):
71
+ """
72
+ Return an instance of a :class:`.Formatter` subclass that has `alias` in its
73
+ aliases list. The formatter is given the `options` at its instantiation.
74
+
75
+ Will raise :exc:`pygments.util.ClassNotFound` if no formatter with that
76
+ alias is found.
77
+ """
78
+ cls = find_formatter_class(_alias)
79
+ if cls is None:
80
+ raise ClassNotFound(f"no formatter found for name {_alias!r}")
81
+ return cls(**options)
82
+
83
+
84
+ def load_formatter_from_file(filename, formattername="CustomFormatter", **options):
85
+ """
86
+ Return a `Formatter` subclass instance loaded from the provided file, relative
87
+ to the current directory.
88
+
89
+ The file is expected to contain a Formatter class named ``formattername``
90
+ (by default, CustomFormatter). Users should be very careful with the input, because
91
+ this method is equivalent to running ``eval()`` on the input file. The formatter is
92
+ given the `options` at its instantiation.
93
+
94
+ :exc:`pygments.util.ClassNotFound` is raised if there are any errors loading
95
+ the formatter.
96
+
97
+ .. versionadded:: 2.2
98
+ """
99
+ try:
100
+ # This empty dict will contain the namespace for the exec'd file
101
+ custom_namespace = {}
102
+ with open(filename, 'rb') as f:
103
+ exec(f.read(), custom_namespace)
104
+ # Retrieve the class `formattername` from that namespace
105
+ if formattername not in custom_namespace:
106
+ raise ClassNotFound(f'no valid {formattername} class found in {filename}')
107
+ formatter_class = custom_namespace[formattername]
108
+ # And finally instantiate it with the options
109
+ return formatter_class(**options)
110
+ except OSError as err:
111
+ raise ClassNotFound(f'cannot read {filename}: {err}')
112
+ except ClassNotFound:
113
+ raise
114
+ except Exception as err:
115
+ raise ClassNotFound(f'error when loading custom formatter: {err}')
116
+
117
+
118
+ def get_formatter_for_filename(fn, **options):
119
+ """
120
+ Return a :class:`.Formatter` subclass instance that has a filename pattern
121
+ matching `fn`. The formatter is given the `options` at its instantiation.
122
+
123
+ Will raise :exc:`pygments.util.ClassNotFound` if no formatter for that filename
124
+ is found.
125
+ """
126
+ fn = basename(fn)
127
+ for modname, name, _, filenames, _ in FORMATTERS.values():
128
+ for filename in filenames:
129
+ if _fn_matches(fn, filename):
130
+ if name not in _formatter_cache:
131
+ _load_formatters(modname)
132
+ return _formatter_cache[name](**options)
133
+ for _name, cls in find_plugin_formatters():
134
+ for filename in cls.filenames:
135
+ if _fn_matches(fn, filename):
136
+ return cls(**options)
137
+ raise ClassNotFound(f"no formatter found for file name {fn!r}")
138
+
139
+
140
+ class _automodule(types.ModuleType):
141
+ """Automatically import formatters."""
142
+
143
+ def __getattr__(self, name):
144
+ info = FORMATTERS.get(name)
145
+ if info:
146
+ _load_formatters(info[0])
147
+ cls = _formatter_cache[info[1]]
148
+ setattr(self, name, cls)
149
+ return cls
150
+ raise AttributeError(name)
151
+
152
+
153
+ oldmod = sys.modules[__name__]
154
+ newmod = _automodule(__name__)
155
+ newmod.__dict__.update(oldmod.__dict__)
156
+ sys.modules[__name__] = newmod
157
+ del newmod.newmod, newmod.oldmod, newmod.sys, newmod.types
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (6.91 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc ADDED
Binary file (4.22 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc ADDED
Binary file (4.23 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc ADDED
Binary file (7.3 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc ADDED
Binary file (41 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc ADDED
Binary file (28.6 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc ADDED
Binary file (6.06 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc ADDED
Binary file (20.1 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc ADDED
Binary file (6.89 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc ADDED
Binary file (2.97 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc ADDED
Binary file (13.8 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc ADDED
Binary file (9.15 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc ADDED
Binary file (5.83 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc ADDED
Binary file (15.1 kB). View file
 
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Automatically generated by scripts/gen_mapfiles.py.
2
+ # DO NOT EDIT BY HAND; run `tox -e mapfiles` instead.
3
+
4
+ FORMATTERS = {
5
+ 'BBCodeFormatter': ('pygments.formatters.bbcode', 'BBCode', ('bbcode', 'bb'), (), 'Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.'),
6
+ 'BmpImageFormatter': ('pygments.formatters.img', 'img_bmp', ('bmp', 'bitmap'), ('*.bmp',), 'Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
7
+ 'GifImageFormatter': ('pygments.formatters.img', 'img_gif', ('gif',), ('*.gif',), 'Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
8
+ 'GroffFormatter': ('pygments.formatters.groff', 'groff', ('groff', 'troff', 'roff'), (), 'Format tokens with groff escapes to change their color and font style.'),
9
+ 'HtmlFormatter': ('pygments.formatters.html', 'HTML', ('html',), ('*.html', '*.htm'), "Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option). The ``<div>``'s CSS class can be set by the `cssclass` option."),
10
+ 'IRCFormatter': ('pygments.formatters.irc', 'IRC', ('irc', 'IRC'), (), 'Format tokens with IRC color sequences'),
11
+ 'ImageFormatter': ('pygments.formatters.img', 'img', ('img', 'IMG', 'png'), ('*.png',), 'Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
12
+ 'JpgImageFormatter': ('pygments.formatters.img', 'img_jpg', ('jpg', 'jpeg'), ('*.jpg',), 'Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code.'),
13
+ 'LatexFormatter': ('pygments.formatters.latex', 'LaTeX', ('latex', 'tex'), ('*.tex',), 'Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages.'),
14
+ 'NullFormatter': ('pygments.formatters.other', 'Text only', ('text', 'null'), ('*.txt',), 'Output the text unchanged without any formatting.'),
15
+ 'PangoMarkupFormatter': ('pygments.formatters.pangomarkup', 'Pango Markup', ('pango', 'pangomarkup'), (), 'Format tokens as Pango Markup code. It can then be rendered to an SVG.'),
16
+ 'RawTokenFormatter': ('pygments.formatters.other', 'Raw tokens', ('raw', 'tokens'), ('*.raw',), 'Format tokens as a raw representation for storing token streams.'),
17
+ 'RtfFormatter': ('pygments.formatters.rtf', 'RTF', ('rtf',), ('*.rtf',), 'Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents.'),
18
+ 'SvgFormatter': ('pygments.formatters.svg', 'SVG', ('svg',), ('*.svg',), 'Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ``<text>`` element with explicit ``x`` and ``y`` coordinates containing ``<tspan>`` elements with the individual token styles.'),
19
+ 'Terminal256Formatter': ('pygments.formatters.terminal256', 'Terminal256', ('terminal256', 'console256', '256'), (), 'Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
20
+ 'TerminalFormatter': ('pygments.formatters.terminal', 'Terminal', ('terminal', 'console'), (), 'Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly.'),
21
+ 'TerminalTrueColorFormatter': ('pygments.formatters.terminal256', 'TerminalTrueColor', ('terminal16m', 'console16m', '16m'), (), 'Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly.'),
22
+ 'TestcaseFormatter': ('pygments.formatters.other', 'Testcase', ('testcase',), (), 'Format tokens as appropriate for a new testcase.'),
23
+ }
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.bbcode
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ BBcode formatter.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+
12
+ from pip._vendor.pygments.formatter import Formatter
13
+ from pip._vendor.pygments.util import get_bool_opt
14
+
15
+ __all__ = ['BBCodeFormatter']
16
+
17
+
18
+ class BBCodeFormatter(Formatter):
19
+ """
20
+ Format tokens with BBcodes. These formatting codes are used by many
21
+ bulletin boards, so you can highlight your sourcecode with pygments before
22
+ posting it there.
23
+
24
+ This formatter has no support for background colors and borders, as there
25
+ are no common BBcode tags for that.
26
+
27
+ Some board systems (e.g. phpBB) don't support colors in their [code] tag,
28
+ so you can't use the highlighting together with that tag.
29
+ Text in a [code] tag usually is shown with a monospace font (which this
30
+ formatter can do with the ``monofont`` option) and no spaces (which you
31
+ need for indentation) are removed.
32
+
33
+ Additional options accepted:
34
+
35
+ `style`
36
+ The style to use, can be a string or a Style subclass (default:
37
+ ``'default'``).
38
+
39
+ `codetag`
40
+ If set to true, put the output into ``[code]`` tags (default:
41
+ ``false``)
42
+
43
+ `monofont`
44
+ If set to true, add a tag to show the code with a monospace font
45
+ (default: ``false``).
46
+ """
47
+ name = 'BBCode'
48
+ aliases = ['bbcode', 'bb']
49
+ filenames = []
50
+
51
+ def __init__(self, **options):
52
+ Formatter.__init__(self, **options)
53
+ self._code = get_bool_opt(options, 'codetag', False)
54
+ self._mono = get_bool_opt(options, 'monofont', False)
55
+
56
+ self.styles = {}
57
+ self._make_styles()
58
+
59
+ def _make_styles(self):
60
+ for ttype, ndef in self.style:
61
+ start = end = ''
62
+ if ndef['color']:
63
+ start += '[color=#{}]'.format(ndef['color'])
64
+ end = '[/color]' + end
65
+ if ndef['bold']:
66
+ start += '[b]'
67
+ end = '[/b]' + end
68
+ if ndef['italic']:
69
+ start += '[i]'
70
+ end = '[/i]' + end
71
+ if ndef['underline']:
72
+ start += '[u]'
73
+ end = '[/u]' + end
74
+ # there are no common BBcodes for background-color and border
75
+
76
+ self.styles[ttype] = start, end
77
+
78
+ def format_unencoded(self, tokensource, outfile):
79
+ if self._code:
80
+ outfile.write('[code]')
81
+ if self._mono:
82
+ outfile.write('[font=monospace]')
83
+
84
+ lastval = ''
85
+ lasttype = None
86
+
87
+ for ttype, value in tokensource:
88
+ while ttype not in self.styles:
89
+ ttype = ttype.parent
90
+ if ttype == lasttype:
91
+ lastval += value
92
+ else:
93
+ if lastval:
94
+ start, end = self.styles[lasttype]
95
+ outfile.write(''.join((start, lastval, end)))
96
+ lastval = value
97
+ lasttype = ttype
98
+
99
+ if lastval:
100
+ start, end = self.styles[lasttype]
101
+ outfile.write(''.join((start, lastval, end)))
102
+
103
+ if self._mono:
104
+ outfile.write('[/font]')
105
+ if self._code:
106
+ outfile.write('[/code]')
107
+ if self._code or self._mono:
108
+ outfile.write('\n')
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.groff
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for groff output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import math
12
+ from pip._vendor.pygments.formatter import Formatter
13
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt
14
+
15
+ __all__ = ['GroffFormatter']
16
+
17
+
18
+ class GroffFormatter(Formatter):
19
+ """
20
+ Format tokens with groff escapes to change their color and font style.
21
+
22
+ .. versionadded:: 2.11
23
+
24
+ Additional options accepted:
25
+
26
+ `style`
27
+ The style to use, can be a string or a Style subclass (default:
28
+ ``'default'``).
29
+
30
+ `monospaced`
31
+ If set to true, monospace font will be used (default: ``true``).
32
+
33
+ `linenos`
34
+ If set to true, print the line numbers (default: ``false``).
35
+
36
+ `wrap`
37
+ Wrap lines to the specified number of characters. Disabled if set to 0
38
+ (default: ``0``).
39
+ """
40
+
41
+ name = 'groff'
42
+ aliases = ['groff','troff','roff']
43
+ filenames = []
44
+
45
+ def __init__(self, **options):
46
+ Formatter.__init__(self, **options)
47
+
48
+ self.monospaced = get_bool_opt(options, 'monospaced', True)
49
+ self.linenos = get_bool_opt(options, 'linenos', False)
50
+ self._lineno = 0
51
+ self.wrap = get_int_opt(options, 'wrap', 0)
52
+ self._linelen = 0
53
+
54
+ self.styles = {}
55
+ self._make_styles()
56
+
57
+
58
+ def _make_styles(self):
59
+ regular = '\\f[CR]' if self.monospaced else '\\f[R]'
60
+ bold = '\\f[CB]' if self.monospaced else '\\f[B]'
61
+ italic = '\\f[CI]' if self.monospaced else '\\f[I]'
62
+
63
+ for ttype, ndef in self.style:
64
+ start = end = ''
65
+ if ndef['color']:
66
+ start += '\\m[{}]'.format(ndef['color'])
67
+ end = '\\m[]' + end
68
+ if ndef['bold']:
69
+ start += bold
70
+ end = regular + end
71
+ if ndef['italic']:
72
+ start += italic
73
+ end = regular + end
74
+ if ndef['bgcolor']:
75
+ start += '\\M[{}]'.format(ndef['bgcolor'])
76
+ end = '\\M[]' + end
77
+
78
+ self.styles[ttype] = start, end
79
+
80
+
81
+ def _define_colors(self, outfile):
82
+ colors = set()
83
+ for _, ndef in self.style:
84
+ if ndef['color'] is not None:
85
+ colors.add(ndef['color'])
86
+
87
+ for color in sorted(colors):
88
+ outfile.write('.defcolor ' + color + ' rgb #' + color + '\n')
89
+
90
+
91
+ def _write_lineno(self, outfile):
92
+ self._lineno += 1
93
+ outfile.write("%s% 4d " % (self._lineno != 1 and '\n' or '', self._lineno))
94
+
95
+
96
+ def _wrap_line(self, line):
97
+ length = len(line.rstrip('\n'))
98
+ space = ' ' if self.linenos else ''
99
+ newline = ''
100
+
101
+ if length > self.wrap:
102
+ for i in range(0, math.floor(length / self.wrap)):
103
+ chunk = line[i*self.wrap:i*self.wrap+self.wrap]
104
+ newline += (chunk + '\n' + space)
105
+ remainder = length % self.wrap
106
+ if remainder > 0:
107
+ newline += line[-remainder-1:]
108
+ self._linelen = remainder
109
+ elif self._linelen + length > self.wrap:
110
+ newline = ('\n' + space) + line
111
+ self._linelen = length
112
+ else:
113
+ newline = line
114
+ self._linelen += length
115
+
116
+ return newline
117
+
118
+
119
+ def _escape_chars(self, text):
120
+ text = text.replace('\\', '\\[u005C]'). \
121
+ replace('.', '\\[char46]'). \
122
+ replace('\'', '\\[u0027]'). \
123
+ replace('`', '\\[u0060]'). \
124
+ replace('~', '\\[u007E]')
125
+ copy = text
126
+
127
+ for char in copy:
128
+ if len(char) != len(char.encode()):
129
+ uni = char.encode('unicode_escape') \
130
+ .decode()[1:] \
131
+ .replace('x', 'u00') \
132
+ .upper()
133
+ text = text.replace(char, '\\[u' + uni[1:] + ']')
134
+
135
+ return text
136
+
137
+
138
+ def format_unencoded(self, tokensource, outfile):
139
+ self._define_colors(outfile)
140
+
141
+ outfile.write('.nf\n\\f[CR]\n')
142
+
143
+ if self.linenos:
144
+ self._write_lineno(outfile)
145
+
146
+ for ttype, value in tokensource:
147
+ while ttype not in self.styles:
148
+ ttype = ttype.parent
149
+ start, end = self.styles[ttype]
150
+
151
+ for line in value.splitlines(True):
152
+ if self.wrap > 0:
153
+ line = self._wrap_line(line)
154
+
155
+ if start and end:
156
+ text = self._escape_chars(line.rstrip('\n'))
157
+ if text != '':
158
+ outfile.write(''.join((start, text, end)))
159
+ else:
160
+ outfile.write(self._escape_chars(line.rstrip('\n')))
161
+
162
+ if line.endswith('\n'):
163
+ if self.linenos:
164
+ self._write_lineno(outfile)
165
+ self._linelen = 0
166
+ else:
167
+ outfile.write('\n')
168
+ self._linelen = 0
169
+
170
+ outfile.write('\n.fi')
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py ADDED
@@ -0,0 +1,987 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.html
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for HTML output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ import functools
12
+ import os
13
+ import sys
14
+ import os.path
15
+ from io import StringIO
16
+
17
+ from pip._vendor.pygments.formatter import Formatter
18
+ from pip._vendor.pygments.token import Token, Text, STANDARD_TYPES
19
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt
20
+
21
+ try:
22
+ import ctags
23
+ except ImportError:
24
+ ctags = None
25
+
26
+ __all__ = ['HtmlFormatter']
27
+
28
+
29
+ _escape_html_table = {
30
+ ord('&'): '&amp;',
31
+ ord('<'): '&lt;',
32
+ ord('>'): '&gt;',
33
+ ord('"'): '&quot;',
34
+ ord("'"): '&#39;',
35
+ }
36
+
37
+
38
+ def escape_html(text, table=_escape_html_table):
39
+ """Escape &, <, > as well as single and double quotes for HTML."""
40
+ return text.translate(table)
41
+
42
+
43
+ def webify(color):
44
+ if color.startswith('calc') or color.startswith('var'):
45
+ return color
46
+ else:
47
+ return '#' + color
48
+
49
+
50
+ def _get_ttype_class(ttype):
51
+ fname = STANDARD_TYPES.get(ttype)
52
+ if fname:
53
+ return fname
54
+ aname = ''
55
+ while fname is None:
56
+ aname = '-' + ttype[-1] + aname
57
+ ttype = ttype.parent
58
+ fname = STANDARD_TYPES.get(ttype)
59
+ return fname + aname
60
+
61
+
62
+ CSSFILE_TEMPLATE = '''\
63
+ /*
64
+ generated by Pygments <https://pygments.org/>
65
+ Copyright 2006-2024 by the Pygments team.
66
+ Licensed under the BSD license, see LICENSE for details.
67
+ */
68
+ %(styledefs)s
69
+ '''
70
+
71
+ DOC_HEADER = '''\
72
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
73
+ "http://www.w3.org/TR/html4/strict.dtd">
74
+ <!--
75
+ generated by Pygments <https://pygments.org/>
76
+ Copyright 2006-2024 by the Pygments team.
77
+ Licensed under the BSD license, see LICENSE for details.
78
+ -->
79
+ <html>
80
+ <head>
81
+ <title>%(title)s</title>
82
+ <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
83
+ <style type="text/css">
84
+ ''' + CSSFILE_TEMPLATE + '''
85
+ </style>
86
+ </head>
87
+ <body>
88
+ <h2>%(title)s</h2>
89
+
90
+ '''
91
+
92
+ DOC_HEADER_EXTERNALCSS = '''\
93
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
94
+ "http://www.w3.org/TR/html4/strict.dtd">
95
+
96
+ <html>
97
+ <head>
98
+ <title>%(title)s</title>
99
+ <meta http-equiv="content-type" content="text/html; charset=%(encoding)s">
100
+ <link rel="stylesheet" href="%(cssfile)s" type="text/css">
101
+ </head>
102
+ <body>
103
+ <h2>%(title)s</h2>
104
+
105
+ '''
106
+
107
+ DOC_FOOTER = '''\
108
+ </body>
109
+ </html>
110
+ '''
111
+
112
+
113
+ class HtmlFormatter(Formatter):
114
+ r"""
115
+ Format tokens as HTML 4 ``<span>`` tags. By default, the content is enclosed
116
+ in a ``<pre>`` tag, itself wrapped in a ``<div>`` tag (but see the `nowrap` option).
117
+ The ``<div>``'s CSS class can be set by the `cssclass` option.
118
+
119
+ If the `linenos` option is set to ``"table"``, the ``<pre>`` is
120
+ additionally wrapped inside a ``<table>`` which has one row and two
121
+ cells: one containing the line numbers and one containing the code.
122
+ Example:
123
+
124
+ .. sourcecode:: html
125
+
126
+ <div class="highlight" >
127
+ <table><tr>
128
+ <td class="linenos" title="click to toggle"
129
+ onclick="with (this.firstChild.style)
130
+ { display = (display == '') ? 'none' : '' }">
131
+ <pre>1
132
+ 2</pre>
133
+ </td>
134
+ <td class="code">
135
+ <pre><span class="Ke">def </span><span class="NaFu">foo</span>(bar):
136
+ <span class="Ke">pass</span>
137
+ </pre>
138
+ </td>
139
+ </tr></table></div>
140
+
141
+ (whitespace added to improve clarity).
142
+
143
+ A list of lines can be specified using the `hl_lines` option to make these
144
+ lines highlighted (as of Pygments 0.11).
145
+
146
+ With the `full` option, a complete HTML 4 document is output, including
147
+ the style definitions inside a ``<style>`` tag, or in a separate file if
148
+ the `cssfile` option is given.
149
+
150
+ When `tagsfile` is set to the path of a ctags index file, it is used to
151
+ generate hyperlinks from names to their definition. You must enable
152
+ `lineanchors` and run ctags with the `-n` option for this to work. The
153
+ `python-ctags` module from PyPI must be installed to use this feature;
154
+ otherwise a `RuntimeError` will be raised.
155
+
156
+ The `get_style_defs(arg='')` method of a `HtmlFormatter` returns a string
157
+ containing CSS rules for the CSS classes used by the formatter. The
158
+ argument `arg` can be used to specify additional CSS selectors that
159
+ are prepended to the classes. A call `fmter.get_style_defs('td .code')`
160
+ would result in the following CSS classes:
161
+
162
+ .. sourcecode:: css
163
+
164
+ td .code .kw { font-weight: bold; color: #00FF00 }
165
+ td .code .cm { color: #999999 }
166
+ ...
167
+
168
+ If you have Pygments 0.6 or higher, you can also pass a list or tuple to the
169
+ `get_style_defs()` method to request multiple prefixes for the tokens:
170
+
171
+ .. sourcecode:: python
172
+
173
+ formatter.get_style_defs(['div.syntax pre', 'pre.syntax'])
174
+
175
+ The output would then look like this:
176
+
177
+ .. sourcecode:: css
178
+
179
+ div.syntax pre .kw,
180
+ pre.syntax .kw { font-weight: bold; color: #00FF00 }
181
+ div.syntax pre .cm,
182
+ pre.syntax .cm { color: #999999 }
183
+ ...
184
+
185
+ Additional options accepted:
186
+
187
+ `nowrap`
188
+ If set to ``True``, don't add a ``<pre>`` and a ``<div>`` tag
189
+ around the tokens. This disables most other options (default: ``False``).
190
+
191
+ `full`
192
+ Tells the formatter to output a "full" document, i.e. a complete
193
+ self-contained document (default: ``False``).
194
+
195
+ `title`
196
+ If `full` is true, the title that should be used to caption the
197
+ document (default: ``''``).
198
+
199
+ `style`
200
+ The style to use, can be a string or a Style subclass (default:
201
+ ``'default'``). This option has no effect if the `cssfile`
202
+ and `noclobber_cssfile` option are given and the file specified in
203
+ `cssfile` exists.
204
+
205
+ `noclasses`
206
+ If set to true, token ``<span>`` tags (as well as line number elements)
207
+ will not use CSS classes, but inline styles. This is not recommended
208
+ for larger pieces of code since it increases output size by quite a bit
209
+ (default: ``False``).
210
+
211
+ `classprefix`
212
+ Since the token types use relatively short class names, they may clash
213
+ with some of your own class names. In this case you can use the
214
+ `classprefix` option to give a string to prepend to all Pygments-generated
215
+ CSS class names for token types.
216
+ Note that this option also affects the output of `get_style_defs()`.
217
+
218
+ `cssclass`
219
+ CSS class for the wrapping ``<div>`` tag (default: ``'highlight'``).
220
+ If you set this option, the default selector for `get_style_defs()`
221
+ will be this class.
222
+
223
+ .. versionadded:: 0.9
224
+ If you select the ``'table'`` line numbers, the wrapping table will
225
+ have a CSS class of this string plus ``'table'``, the default is
226
+ accordingly ``'highlighttable'``.
227
+
228
+ `cssstyles`
229
+ Inline CSS styles for the wrapping ``<div>`` tag (default: ``''``).
230
+
231
+ `prestyles`
232
+ Inline CSS styles for the ``<pre>`` tag (default: ``''``).
233
+
234
+ .. versionadded:: 0.11
235
+
236
+ `cssfile`
237
+ If the `full` option is true and this option is given, it must be the
238
+ name of an external file. If the filename does not include an absolute
239
+ path, the file's path will be assumed to be relative to the main output
240
+ file's path, if the latter can be found. The stylesheet is then written
241
+ to this file instead of the HTML file.
242
+
243
+ .. versionadded:: 0.6
244
+
245
+ `noclobber_cssfile`
246
+ If `cssfile` is given and the specified file exists, the css file will
247
+ not be overwritten. This allows the use of the `full` option in
248
+ combination with a user specified css file. Default is ``False``.
249
+
250
+ .. versionadded:: 1.1
251
+
252
+ `linenos`
253
+ If set to ``'table'``, output line numbers as a table with two cells,
254
+ one containing the line numbers, the other the whole code. This is
255
+ copy-and-paste-friendly, but may cause alignment problems with some
256
+ browsers or fonts. If set to ``'inline'``, the line numbers will be
257
+ integrated in the ``<pre>`` tag that contains the code (that setting
258
+ is *new in Pygments 0.8*).
259
+
260
+ For compatibility with Pygments 0.7 and earlier, every true value
261
+ except ``'inline'`` means the same as ``'table'`` (in particular, that
262
+ means also ``True``).
263
+
264
+ The default value is ``False``, which means no line numbers at all.
265
+
266
+ **Note:** with the default ("table") line number mechanism, the line
267
+ numbers and code can have different line heights in Internet Explorer
268
+ unless you give the enclosing ``<pre>`` tags an explicit ``line-height``
269
+ CSS property (you get the default line spacing with ``line-height:
270
+ 125%``).
271
+
272
+ `hl_lines`
273
+ Specify a list of lines to be highlighted. The line numbers are always
274
+ relative to the input (i.e. the first line is line 1) and are
275
+ independent of `linenostart`.
276
+
277
+ .. versionadded:: 0.11
278
+
279
+ `linenostart`
280
+ The line number for the first line (default: ``1``).
281
+
282
+ `linenostep`
283
+ If set to a number n > 1, only every nth line number is printed.
284
+
285
+ `linenospecial`
286
+ If set to a number n > 0, every nth line number is given the CSS
287
+ class ``"special"`` (default: ``0``).
288
+
289
+ `nobackground`
290
+ If set to ``True``, the formatter won't output the background color
291
+ for the wrapping element (this automatically defaults to ``False``
292
+ when there is no wrapping element [eg: no argument for the
293
+ `get_syntax_defs` method given]) (default: ``False``).
294
+
295
+ .. versionadded:: 0.6
296
+
297
+ `lineseparator`
298
+ This string is output between lines of code. It defaults to ``"\n"``,
299
+ which is enough to break a line inside ``<pre>`` tags, but you can
300
+ e.g. set it to ``"<br>"`` to get HTML line breaks.
301
+
302
+ .. versionadded:: 0.7
303
+
304
+ `lineanchors`
305
+ If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
306
+ output line in an anchor tag with an ``id`` (and `name`) of ``foo-linenumber``.
307
+ This allows easy linking to certain lines.
308
+
309
+ .. versionadded:: 0.9
310
+
311
+ `linespans`
312
+ If set to a nonempty string, e.g. ``foo``, the formatter will wrap each
313
+ output line in a span tag with an ``id`` of ``foo-linenumber``.
314
+ This allows easy access to lines via javascript.
315
+
316
+ .. versionadded:: 1.6
317
+
318
+ `anchorlinenos`
319
+ If set to `True`, will wrap line numbers in <a> tags. Used in
320
+ combination with `linenos` and `lineanchors`.
321
+
322
+ `tagsfile`
323
+ If set to the path of a ctags file, wrap names in anchor tags that
324
+ link to their definitions. `lineanchors` should be used, and the
325
+ tags file should specify line numbers (see the `-n` option to ctags).
326
+ The tags file is assumed to be encoded in UTF-8.
327
+
328
+ .. versionadded:: 1.6
329
+
330
+ `tagurlformat`
331
+ A string formatting pattern used to generate links to ctags definitions.
332
+ Available variables are `%(path)s`, `%(fname)s` and `%(fext)s`.
333
+ Defaults to an empty string, resulting in just `#prefix-number` links.
334
+
335
+ .. versionadded:: 1.6
336
+
337
+ `filename`
338
+ A string used to generate a filename when rendering ``<pre>`` blocks,
339
+ for example if displaying source code. If `linenos` is set to
340
+ ``'table'`` then the filename will be rendered in an initial row
341
+ containing a single `<th>` which spans both columns.
342
+
343
+ .. versionadded:: 2.1
344
+
345
+ `wrapcode`
346
+ Wrap the code inside ``<pre>`` blocks using ``<code>``, as recommended
347
+ by the HTML5 specification.
348
+
349
+ .. versionadded:: 2.4
350
+
351
+ `debug_token_types`
352
+ Add ``title`` attributes to all token ``<span>`` tags that show the
353
+ name of the token.
354
+
355
+ .. versionadded:: 2.10
356
+
357
+
358
+ **Subclassing the HTML formatter**
359
+
360
+ .. versionadded:: 0.7
361
+
362
+ The HTML formatter is now built in a way that allows easy subclassing, thus
363
+ customizing the output HTML code. The `format()` method calls
364
+ `self._format_lines()` which returns a generator that yields tuples of ``(1,
365
+ line)``, where the ``1`` indicates that the ``line`` is a line of the
366
+ formatted source code.
367
+
368
+ If the `nowrap` option is set, the generator is the iterated over and the
369
+ resulting HTML is output.
370
+
371
+ Otherwise, `format()` calls `self.wrap()`, which wraps the generator with
372
+ other generators. These may add some HTML code to the one generated by
373
+ `_format_lines()`, either by modifying the lines generated by the latter,
374
+ then yielding them again with ``(1, line)``, and/or by yielding other HTML
375
+ code before or after the lines, with ``(0, html)``. The distinction between
376
+ source lines and other code makes it possible to wrap the generator multiple
377
+ times.
378
+
379
+ The default `wrap()` implementation adds a ``<div>`` and a ``<pre>`` tag.
380
+
381
+ A custom `HtmlFormatter` subclass could look like this:
382
+
383
+ .. sourcecode:: python
384
+
385
+ class CodeHtmlFormatter(HtmlFormatter):
386
+
387
+ def wrap(self, source, *, include_div):
388
+ return self._wrap_code(source)
389
+
390
+ def _wrap_code(self, source):
391
+ yield 0, '<code>'
392
+ for i, t in source:
393
+ if i == 1:
394
+ # it's a line of formatted code
395
+ t += '<br>'
396
+ yield i, t
397
+ yield 0, '</code>'
398
+
399
+ This results in wrapping the formatted lines with a ``<code>`` tag, where the
400
+ source lines are broken using ``<br>`` tags.
401
+
402
+ After calling `wrap()`, the `format()` method also adds the "line numbers"
403
+ and/or "full document" wrappers if the respective options are set. Then, all
404
+ HTML yielded by the wrapped generator is output.
405
+ """
406
+
407
+ name = 'HTML'
408
+ aliases = ['html']
409
+ filenames = ['*.html', '*.htm']
410
+
411
+ def __init__(self, **options):
412
+ Formatter.__init__(self, **options)
413
+ self.title = self._decodeifneeded(self.title)
414
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
415
+ self.noclasses = get_bool_opt(options, 'noclasses', False)
416
+ self.classprefix = options.get('classprefix', '')
417
+ self.cssclass = self._decodeifneeded(options.get('cssclass', 'highlight'))
418
+ self.cssstyles = self._decodeifneeded(options.get('cssstyles', ''))
419
+ self.prestyles = self._decodeifneeded(options.get('prestyles', ''))
420
+ self.cssfile = self._decodeifneeded(options.get('cssfile', ''))
421
+ self.noclobber_cssfile = get_bool_opt(options, 'noclobber_cssfile', False)
422
+ self.tagsfile = self._decodeifneeded(options.get('tagsfile', ''))
423
+ self.tagurlformat = self._decodeifneeded(options.get('tagurlformat', ''))
424
+ self.filename = self._decodeifneeded(options.get('filename', ''))
425
+ self.wrapcode = get_bool_opt(options, 'wrapcode', False)
426
+ self.span_element_openers = {}
427
+ self.debug_token_types = get_bool_opt(options, 'debug_token_types', False)
428
+
429
+ if self.tagsfile:
430
+ if not ctags:
431
+ raise RuntimeError('The "ctags" package must to be installed '
432
+ 'to be able to use the "tagsfile" feature.')
433
+ self._ctags = ctags.CTags(self.tagsfile)
434
+
435
+ linenos = options.get('linenos', False)
436
+ if linenos == 'inline':
437
+ self.linenos = 2
438
+ elif linenos:
439
+ # compatibility with <= 0.7
440
+ self.linenos = 1
441
+ else:
442
+ self.linenos = 0
443
+ self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
444
+ self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
445
+ self.linenospecial = abs(get_int_opt(options, 'linenospecial', 0))
446
+ self.nobackground = get_bool_opt(options, 'nobackground', False)
447
+ self.lineseparator = options.get('lineseparator', '\n')
448
+ self.lineanchors = options.get('lineanchors', '')
449
+ self.linespans = options.get('linespans', '')
450
+ self.anchorlinenos = get_bool_opt(options, 'anchorlinenos', False)
451
+ self.hl_lines = set()
452
+ for lineno in get_list_opt(options, 'hl_lines', []):
453
+ try:
454
+ self.hl_lines.add(int(lineno))
455
+ except ValueError:
456
+ pass
457
+
458
+ self._create_stylesheet()
459
+
460
+ def _get_css_class(self, ttype):
461
+ """Return the css class of this token type prefixed with
462
+ the classprefix option."""
463
+ ttypeclass = _get_ttype_class(ttype)
464
+ if ttypeclass:
465
+ return self.classprefix + ttypeclass
466
+ return ''
467
+
468
+ def _get_css_classes(self, ttype):
469
+ """Return the CSS classes of this token type prefixed with the classprefix option."""
470
+ cls = self._get_css_class(ttype)
471
+ while ttype not in STANDARD_TYPES:
472
+ ttype = ttype.parent
473
+ cls = self._get_css_class(ttype) + ' ' + cls
474
+ return cls or ''
475
+
476
+ def _get_css_inline_styles(self, ttype):
477
+ """Return the inline CSS styles for this token type."""
478
+ cclass = self.ttype2class.get(ttype)
479
+ while cclass is None:
480
+ ttype = ttype.parent
481
+ cclass = self.ttype2class.get(ttype)
482
+ return cclass or ''
483
+
484
+ def _create_stylesheet(self):
485
+ t2c = self.ttype2class = {Token: ''}
486
+ c2s = self.class2style = {}
487
+ for ttype, ndef in self.style:
488
+ name = self._get_css_class(ttype)
489
+ style = ''
490
+ if ndef['color']:
491
+ style += 'color: {}; '.format(webify(ndef['color']))
492
+ if ndef['bold']:
493
+ style += 'font-weight: bold; '
494
+ if ndef['italic']:
495
+ style += 'font-style: italic; '
496
+ if ndef['underline']:
497
+ style += 'text-decoration: underline; '
498
+ if ndef['bgcolor']:
499
+ style += 'background-color: {}; '.format(webify(ndef['bgcolor']))
500
+ if ndef['border']:
501
+ style += 'border: 1px solid {}; '.format(webify(ndef['border']))
502
+ if style:
503
+ t2c[ttype] = name
504
+ # save len(ttype) to enable ordering the styles by
505
+ # hierarchy (necessary for CSS cascading rules!)
506
+ c2s[name] = (style[:-2], ttype, len(ttype))
507
+
508
+ def get_style_defs(self, arg=None):
509
+ """
510
+ Return CSS style definitions for the classes produced by the current
511
+ highlighting style. ``arg`` can be a string or list of selectors to
512
+ insert before the token type classes.
513
+ """
514
+ style_lines = []
515
+
516
+ style_lines.extend(self.get_linenos_style_defs())
517
+ style_lines.extend(self.get_background_style_defs(arg))
518
+ style_lines.extend(self.get_token_style_defs(arg))
519
+
520
+ return '\n'.join(style_lines)
521
+
522
+ def get_token_style_defs(self, arg=None):
523
+ prefix = self.get_css_prefix(arg)
524
+
525
+ styles = [
526
+ (level, ttype, cls, style)
527
+ for cls, (style, ttype, level) in self.class2style.items()
528
+ if cls and style
529
+ ]
530
+ styles.sort()
531
+
532
+ lines = [
533
+ f'{prefix(cls)} {{ {style} }} /* {repr(ttype)[6:]} */'
534
+ for (level, ttype, cls, style) in styles
535
+ ]
536
+
537
+ return lines
538
+
539
+ def get_background_style_defs(self, arg=None):
540
+ prefix = self.get_css_prefix(arg)
541
+ bg_color = self.style.background_color
542
+ hl_color = self.style.highlight_color
543
+
544
+ lines = []
545
+
546
+ if arg and not self.nobackground and bg_color is not None:
547
+ text_style = ''
548
+ if Text in self.ttype2class:
549
+ text_style = ' ' + self.class2style[self.ttype2class[Text]][0]
550
+ lines.insert(
551
+ 0, '{}{{ background: {};{} }}'.format(
552
+ prefix(''), bg_color, text_style
553
+ )
554
+ )
555
+ if hl_color is not None:
556
+ lines.insert(
557
+ 0, '{} {{ background-color: {} }}'.format(prefix('hll'), hl_color)
558
+ )
559
+
560
+ return lines
561
+
562
+ def get_linenos_style_defs(self):
563
+ lines = [
564
+ f'pre {{ {self._pre_style} }}',
565
+ f'td.linenos .normal {{ {self._linenos_style} }}',
566
+ f'span.linenos {{ {self._linenos_style} }}',
567
+ f'td.linenos .special {{ {self._linenos_special_style} }}',
568
+ f'span.linenos.special {{ {self._linenos_special_style} }}',
569
+ ]
570
+
571
+ return lines
572
+
573
+ def get_css_prefix(self, arg):
574
+ if arg is None:
575
+ arg = ('cssclass' in self.options and '.'+self.cssclass or '')
576
+ if isinstance(arg, str):
577
+ args = [arg]
578
+ else:
579
+ args = list(arg)
580
+
581
+ def prefix(cls):
582
+ if cls:
583
+ cls = '.' + cls
584
+ tmp = []
585
+ for arg in args:
586
+ tmp.append((arg and arg + ' ' or '') + cls)
587
+ return ', '.join(tmp)
588
+
589
+ return prefix
590
+
591
+ @property
592
+ def _pre_style(self):
593
+ return 'line-height: 125%;'
594
+
595
+ @property
596
+ def _linenos_style(self):
597
+ color = self.style.line_number_color
598
+ background_color = self.style.line_number_background_color
599
+ return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'
600
+
601
+ @property
602
+ def _linenos_special_style(self):
603
+ color = self.style.line_number_special_color
604
+ background_color = self.style.line_number_special_background_color
605
+ return f'color: {color}; background-color: {background_color}; padding-left: 5px; padding-right: 5px;'
606
+
607
+ def _decodeifneeded(self, value):
608
+ if isinstance(value, bytes):
609
+ if self.encoding:
610
+ return value.decode(self.encoding)
611
+ return value.decode()
612
+ return value
613
+
614
+ def _wrap_full(self, inner, outfile):
615
+ if self.cssfile:
616
+ if os.path.isabs(self.cssfile):
617
+ # it's an absolute filename
618
+ cssfilename = self.cssfile
619
+ else:
620
+ try:
621
+ filename = outfile.name
622
+ if not filename or filename[0] == '<':
623
+ # pseudo files, e.g. name == '<fdopen>'
624
+ raise AttributeError
625
+ cssfilename = os.path.join(os.path.dirname(filename),
626
+ self.cssfile)
627
+ except AttributeError:
628
+ print('Note: Cannot determine output file name, '
629
+ 'using current directory as base for the CSS file name',
630
+ file=sys.stderr)
631
+ cssfilename = self.cssfile
632
+ # write CSS file only if noclobber_cssfile isn't given as an option.
633
+ try:
634
+ if not os.path.exists(cssfilename) or not self.noclobber_cssfile:
635
+ with open(cssfilename, "w", encoding="utf-8") as cf:
636
+ cf.write(CSSFILE_TEMPLATE %
637
+ {'styledefs': self.get_style_defs('body')})
638
+ except OSError as err:
639
+ err.strerror = 'Error writing CSS file: ' + err.strerror
640
+ raise
641
+
642
+ yield 0, (DOC_HEADER_EXTERNALCSS %
643
+ dict(title=self.title,
644
+ cssfile=self.cssfile,
645
+ encoding=self.encoding))
646
+ else:
647
+ yield 0, (DOC_HEADER %
648
+ dict(title=self.title,
649
+ styledefs=self.get_style_defs('body'),
650
+ encoding=self.encoding))
651
+
652
+ yield from inner
653
+ yield 0, DOC_FOOTER
654
+
655
+ def _wrap_tablelinenos(self, inner):
656
+ dummyoutfile = StringIO()
657
+ lncount = 0
658
+ for t, line in inner:
659
+ if t:
660
+ lncount += 1
661
+ dummyoutfile.write(line)
662
+
663
+ fl = self.linenostart
664
+ mw = len(str(lncount + fl - 1))
665
+ sp = self.linenospecial
666
+ st = self.linenostep
667
+ anchor_name = self.lineanchors or self.linespans
668
+ aln = self.anchorlinenos
669
+ nocls = self.noclasses
670
+
671
+ lines = []
672
+
673
+ for i in range(fl, fl+lncount):
674
+ print_line = i % st == 0
675
+ special_line = sp and i % sp == 0
676
+
677
+ if print_line:
678
+ line = '%*d' % (mw, i)
679
+ if aln:
680
+ line = '<a href="#%s-%d">%s</a>' % (anchor_name, i, line)
681
+ else:
682
+ line = ' ' * mw
683
+
684
+ if nocls:
685
+ if special_line:
686
+ style = f' style="{self._linenos_special_style}"'
687
+ else:
688
+ style = f' style="{self._linenos_style}"'
689
+ else:
690
+ if special_line:
691
+ style = ' class="special"'
692
+ else:
693
+ style = ' class="normal"'
694
+
695
+ if style:
696
+ line = f'<span{style}>{line}</span>'
697
+
698
+ lines.append(line)
699
+
700
+ ls = '\n'.join(lines)
701
+
702
+ # If a filename was specified, we can't put it into the code table as it
703
+ # would misalign the line numbers. Hence we emit a separate row for it.
704
+ filename_tr = ""
705
+ if self.filename:
706
+ filename_tr = (
707
+ '<tr><th colspan="2" class="filename">'
708
+ '<span class="filename">' + self.filename + '</span>'
709
+ '</th></tr>')
710
+
711
+ # in case you wonder about the seemingly redundant <div> here: since the
712
+ # content in the other cell also is wrapped in a div, some browsers in
713
+ # some configurations seem to mess up the formatting...
714
+ yield 0, (f'<table class="{self.cssclass}table">' + filename_tr +
715
+ '<tr><td class="linenos"><div class="linenodiv"><pre>' +
716
+ ls + '</pre></div></td><td class="code">')
717
+ yield 0, '<div>'
718
+ yield 0, dummyoutfile.getvalue()
719
+ yield 0, '</div>'
720
+ yield 0, '</td></tr></table>'
721
+
722
+
723
+ def _wrap_inlinelinenos(self, inner):
724
+ # need a list of lines since we need the width of a single number :(
725
+ inner_lines = list(inner)
726
+ sp = self.linenospecial
727
+ st = self.linenostep
728
+ num = self.linenostart
729
+ mw = len(str(len(inner_lines) + num - 1))
730
+ anchor_name = self.lineanchors or self.linespans
731
+ aln = self.anchorlinenos
732
+ nocls = self.noclasses
733
+
734
+ for _, inner_line in inner_lines:
735
+ print_line = num % st == 0
736
+ special_line = sp and num % sp == 0
737
+
738
+ if print_line:
739
+ line = '%*d' % (mw, num)
740
+ else:
741
+ line = ' ' * mw
742
+
743
+ if nocls:
744
+ if special_line:
745
+ style = f' style="{self._linenos_special_style}"'
746
+ else:
747
+ style = f' style="{self._linenos_style}"'
748
+ else:
749
+ if special_line:
750
+ style = ' class="linenos special"'
751
+ else:
752
+ style = ' class="linenos"'
753
+
754
+ if style:
755
+ linenos = f'<span{style}>{line}</span>'
756
+ else:
757
+ linenos = line
758
+
759
+ if aln:
760
+ yield 1, ('<a href="#%s-%d">%s</a>' % (anchor_name, num, linenos) +
761
+ inner_line)
762
+ else:
763
+ yield 1, linenos + inner_line
764
+ num += 1
765
+
766
+ def _wrap_lineanchors(self, inner):
767
+ s = self.lineanchors
768
+ # subtract 1 since we have to increment i *before* yielding
769
+ i = self.linenostart - 1
770
+ for t, line in inner:
771
+ if t:
772
+ i += 1
773
+ href = "" if self.linenos else ' href="#%s-%d"' % (s, i)
774
+ yield 1, '<a id="%s-%d" name="%s-%d"%s></a>' % (s, i, s, i, href) + line
775
+ else:
776
+ yield 0, line
777
+
778
+ def _wrap_linespans(self, inner):
779
+ s = self.linespans
780
+ i = self.linenostart - 1
781
+ for t, line in inner:
782
+ if t:
783
+ i += 1
784
+ yield 1, '<span id="%s-%d">%s</span>' % (s, i, line)
785
+ else:
786
+ yield 0, line
787
+
788
+ def _wrap_div(self, inner):
789
+ style = []
790
+ if (self.noclasses and not self.nobackground and
791
+ self.style.background_color is not None):
792
+ style.append(f'background: {self.style.background_color}')
793
+ if self.cssstyles:
794
+ style.append(self.cssstyles)
795
+ style = '; '.join(style)
796
+
797
+ yield 0, ('<div' + (self.cssclass and f' class="{self.cssclass}"') +
798
+ (style and (f' style="{style}"')) + '>')
799
+ yield from inner
800
+ yield 0, '</div>\n'
801
+
802
+ def _wrap_pre(self, inner):
803
+ style = []
804
+ if self.prestyles:
805
+ style.append(self.prestyles)
806
+ if self.noclasses:
807
+ style.append(self._pre_style)
808
+ style = '; '.join(style)
809
+
810
+ if self.filename and self.linenos != 1:
811
+ yield 0, ('<span class="filename">' + self.filename + '</span>')
812
+
813
+ # the empty span here is to keep leading empty lines from being
814
+ # ignored by HTML parsers
815
+ yield 0, ('<pre' + (style and f' style="{style}"') + '><span></span>')
816
+ yield from inner
817
+ yield 0, '</pre>'
818
+
819
+ def _wrap_code(self, inner):
820
+ yield 0, '<code>'
821
+ yield from inner
822
+ yield 0, '</code>'
823
+
824
+ @functools.lru_cache(maxsize=100)
825
+ def _translate_parts(self, value):
826
+ """HTML-escape a value and split it by newlines."""
827
+ return value.translate(_escape_html_table).split('\n')
828
+
829
+ def _format_lines(self, tokensource):
830
+ """
831
+ Just format the tokens, without any wrapping tags.
832
+ Yield individual lines.
833
+ """
834
+ nocls = self.noclasses
835
+ lsep = self.lineseparator
836
+ tagsfile = self.tagsfile
837
+
838
+ lspan = ''
839
+ line = []
840
+ for ttype, value in tokensource:
841
+ try:
842
+ cspan = self.span_element_openers[ttype]
843
+ except KeyError:
844
+ title = ' title="{}"'.format('.'.join(ttype)) if self.debug_token_types else ''
845
+ if nocls:
846
+ css_style = self._get_css_inline_styles(ttype)
847
+ if css_style:
848
+ css_style = self.class2style[css_style][0]
849
+ cspan = f'<span style="{css_style}"{title}>'
850
+ else:
851
+ cspan = ''
852
+ else:
853
+ css_class = self._get_css_classes(ttype)
854
+ if css_class:
855
+ cspan = f'<span class="{css_class}"{title}>'
856
+ else:
857
+ cspan = ''
858
+ self.span_element_openers[ttype] = cspan
859
+
860
+ parts = self._translate_parts(value)
861
+
862
+ if tagsfile and ttype in Token.Name:
863
+ filename, linenumber = self._lookup_ctag(value)
864
+ if linenumber:
865
+ base, filename = os.path.split(filename)
866
+ if base:
867
+ base += '/'
868
+ filename, extension = os.path.splitext(filename)
869
+ url = self.tagurlformat % {'path': base, 'fname': filename,
870
+ 'fext': extension}
871
+ parts[0] = "<a href=\"%s#%s-%d\">%s" % \
872
+ (url, self.lineanchors, linenumber, parts[0])
873
+ parts[-1] = parts[-1] + "</a>"
874
+
875
+ # for all but the last line
876
+ for part in parts[:-1]:
877
+ if line:
878
+ # Also check for part being non-empty, so we avoid creating
879
+ # empty <span> tags
880
+ if lspan != cspan and part:
881
+ line.extend(((lspan and '</span>'), cspan, part,
882
+ (cspan and '</span>'), lsep))
883
+ else: # both are the same, or the current part was empty
884
+ line.extend((part, (lspan and '</span>'), lsep))
885
+ yield 1, ''.join(line)
886
+ line = []
887
+ elif part:
888
+ yield 1, ''.join((cspan, part, (cspan and '</span>'), lsep))
889
+ else:
890
+ yield 1, lsep
891
+ # for the last line
892
+ if line and parts[-1]:
893
+ if lspan != cspan:
894
+ line.extend(((lspan and '</span>'), cspan, parts[-1]))
895
+ lspan = cspan
896
+ else:
897
+ line.append(parts[-1])
898
+ elif parts[-1]:
899
+ line = [cspan, parts[-1]]
900
+ lspan = cspan
901
+ # else we neither have to open a new span nor set lspan
902
+
903
+ if line:
904
+ line.extend(((lspan and '</span>'), lsep))
905
+ yield 1, ''.join(line)
906
+
907
+ def _lookup_ctag(self, token):
908
+ entry = ctags.TagEntry()
909
+ if self._ctags.find(entry, token.encode(), 0):
910
+ return entry['file'].decode(), entry['lineNumber']
911
+ else:
912
+ return None, None
913
+
914
+ def _highlight_lines(self, tokensource):
915
+ """
916
+ Highlighted the lines specified in the `hl_lines` option by
917
+ post-processing the token stream coming from `_format_lines`.
918
+ """
919
+ hls = self.hl_lines
920
+
921
+ for i, (t, value) in enumerate(tokensource):
922
+ if t != 1:
923
+ yield t, value
924
+ if i + 1 in hls: # i + 1 because Python indexes start at 0
925
+ if self.noclasses:
926
+ style = ''
927
+ if self.style.highlight_color is not None:
928
+ style = (f' style="background-color: {self.style.highlight_color}"')
929
+ yield 1, f'<span{style}>{value}</span>'
930
+ else:
931
+ yield 1, f'<span class="hll">{value}</span>'
932
+ else:
933
+ yield 1, value
934
+
935
+ def wrap(self, source):
936
+ """
937
+ Wrap the ``source``, which is a generator yielding
938
+ individual lines, in custom generators. See docstring
939
+ for `format`. Can be overridden.
940
+ """
941
+
942
+ output = source
943
+ if self.wrapcode:
944
+ output = self._wrap_code(output)
945
+
946
+ output = self._wrap_pre(output)
947
+
948
+ return output
949
+
950
+ def format_unencoded(self, tokensource, outfile):
951
+ """
952
+ The formatting process uses several nested generators; which of
953
+ them are used is determined by the user's options.
954
+
955
+ Each generator should take at least one argument, ``inner``,
956
+ and wrap the pieces of text generated by this.
957
+
958
+ Always yield 2-tuples: (code, text). If "code" is 1, the text
959
+ is part of the original tokensource being highlighted, if it's
960
+ 0, the text is some piece of wrapping. This makes it possible to
961
+ use several different wrappers that process the original source
962
+ linewise, e.g. line number generators.
963
+ """
964
+ source = self._format_lines(tokensource)
965
+
966
+ # As a special case, we wrap line numbers before line highlighting
967
+ # so the line numbers get wrapped in the highlighting tag.
968
+ if not self.nowrap and self.linenos == 2:
969
+ source = self._wrap_inlinelinenos(source)
970
+
971
+ if self.hl_lines:
972
+ source = self._highlight_lines(source)
973
+
974
+ if not self.nowrap:
975
+ if self.lineanchors:
976
+ source = self._wrap_lineanchors(source)
977
+ if self.linespans:
978
+ source = self._wrap_linespans(source)
979
+ source = self.wrap(source)
980
+ if self.linenos == 1:
981
+ source = self._wrap_tablelinenos(source)
982
+ source = self._wrap_div(source)
983
+ if self.full:
984
+ source = self._wrap_full(source, outfile)
985
+
986
+ for t, piece in source:
987
+ outfile.write(piece)
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.img
3
+ ~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for Pixmap output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+ import os
11
+ import sys
12
+
13
+ from pip._vendor.pygments.formatter import Formatter
14
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, \
15
+ get_choice_opt
16
+
17
+ import subprocess
18
+
19
+ # Import this carefully
20
+ try:
21
+ from PIL import Image, ImageDraw, ImageFont
22
+ pil_available = True
23
+ except ImportError:
24
+ pil_available = False
25
+
26
+ try:
27
+ import _winreg
28
+ except ImportError:
29
+ try:
30
+ import winreg as _winreg
31
+ except ImportError:
32
+ _winreg = None
33
+
34
+ __all__ = ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
35
+ 'BmpImageFormatter']
36
+
37
+
38
+ # For some unknown reason every font calls it something different
39
+ STYLES = {
40
+ 'NORMAL': ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
41
+ 'ITALIC': ['Oblique', 'Italic'],
42
+ 'BOLD': ['Bold'],
43
+ 'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
44
+ }
45
+
46
+ # A sane default for modern systems
47
+ DEFAULT_FONT_NAME_NIX = 'DejaVu Sans Mono'
48
+ DEFAULT_FONT_NAME_WIN = 'Courier New'
49
+ DEFAULT_FONT_NAME_MAC = 'Menlo'
50
+
51
+
52
+ class PilNotAvailable(ImportError):
53
+ """When Python imaging library is not available"""
54
+
55
+
56
+ class FontNotFound(Exception):
57
+ """When there are no usable fonts specified"""
58
+
59
+
60
+ class FontManager:
61
+ """
62
+ Manages a set of fonts: normal, italic, bold, etc...
63
+ """
64
+
65
+ def __init__(self, font_name, font_size=14):
66
+ self.font_name = font_name
67
+ self.font_size = font_size
68
+ self.fonts = {}
69
+ self.encoding = None
70
+ self.variable = False
71
+ if hasattr(font_name, 'read') or os.path.isfile(font_name):
72
+ font = ImageFont.truetype(font_name, self.font_size)
73
+ self.variable = True
74
+ for style in STYLES:
75
+ self.fonts[style] = font
76
+
77
+ return
78
+
79
+ if sys.platform.startswith('win'):
80
+ if not font_name:
81
+ self.font_name = DEFAULT_FONT_NAME_WIN
82
+ self._create_win()
83
+ elif sys.platform.startswith('darwin'):
84
+ if not font_name:
85
+ self.font_name = DEFAULT_FONT_NAME_MAC
86
+ self._create_mac()
87
+ else:
88
+ if not font_name:
89
+ self.font_name = DEFAULT_FONT_NAME_NIX
90
+ self._create_nix()
91
+
92
+ def _get_nix_font_path(self, name, style):
93
+ proc = subprocess.Popen(['fc-list', f"{name}:style={style}", 'file'],
94
+ stdout=subprocess.PIPE, stderr=None)
95
+ stdout, _ = proc.communicate()
96
+ if proc.returncode == 0:
97
+ lines = stdout.splitlines()
98
+ for line in lines:
99
+ if line.startswith(b'Fontconfig warning:'):
100
+ continue
101
+ path = line.decode().strip().strip(':')
102
+ if path:
103
+ return path
104
+ return None
105
+
106
+ def _create_nix(self):
107
+ for name in STYLES['NORMAL']:
108
+ path = self._get_nix_font_path(self.font_name, name)
109
+ if path is not None:
110
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
111
+ break
112
+ else:
113
+ raise FontNotFound(f'No usable fonts named: "{self.font_name}"')
114
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
115
+ for stylename in STYLES[style]:
116
+ path = self._get_nix_font_path(self.font_name, stylename)
117
+ if path is not None:
118
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
119
+ break
120
+ else:
121
+ if style == 'BOLDITALIC':
122
+ self.fonts[style] = self.fonts['BOLD']
123
+ else:
124
+ self.fonts[style] = self.fonts['NORMAL']
125
+
126
+ def _get_mac_font_path(self, font_map, name, style):
127
+ return font_map.get((name + ' ' + style).strip().lower())
128
+
129
+ def _create_mac(self):
130
+ font_map = {}
131
+ for font_dir in (os.path.join(os.getenv("HOME"), 'Library/Fonts/'),
132
+ '/Library/Fonts/', '/System/Library/Fonts/'):
133
+ font_map.update(
134
+ (os.path.splitext(f)[0].lower(), os.path.join(font_dir, f))
135
+ for f in os.listdir(font_dir)
136
+ if f.lower().endswith(('ttf', 'ttc')))
137
+
138
+ for name in STYLES['NORMAL']:
139
+ path = self._get_mac_font_path(font_map, self.font_name, name)
140
+ if path is not None:
141
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
142
+ break
143
+ else:
144
+ raise FontNotFound(f'No usable fonts named: "{self.font_name}"')
145
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
146
+ for stylename in STYLES[style]:
147
+ path = self._get_mac_font_path(font_map, self.font_name, stylename)
148
+ if path is not None:
149
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
150
+ break
151
+ else:
152
+ if style == 'BOLDITALIC':
153
+ self.fonts[style] = self.fonts['BOLD']
154
+ else:
155
+ self.fonts[style] = self.fonts['NORMAL']
156
+
157
+ def _lookup_win(self, key, basename, styles, fail=False):
158
+ for suffix in ('', ' (TrueType)'):
159
+ for style in styles:
160
+ try:
161
+ valname = '{}{}{}'.format(basename, style and ' '+style, suffix)
162
+ val, _ = _winreg.QueryValueEx(key, valname)
163
+ return val
164
+ except OSError:
165
+ continue
166
+ else:
167
+ if fail:
168
+ raise FontNotFound(f'Font {basename} ({styles[0]}) not found in registry')
169
+ return None
170
+
171
+ def _create_win(self):
172
+ lookuperror = None
173
+ keynames = [ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
174
+ (_winreg.HKEY_CURRENT_USER, r'Software\Microsoft\Windows\CurrentVersion\Fonts'),
175
+ (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
176
+ (_winreg.HKEY_LOCAL_MACHINE, r'Software\Microsoft\Windows\CurrentVersion\Fonts') ]
177
+ for keyname in keynames:
178
+ try:
179
+ key = _winreg.OpenKey(*keyname)
180
+ try:
181
+ path = self._lookup_win(key, self.font_name, STYLES['NORMAL'], True)
182
+ self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
183
+ for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
184
+ path = self._lookup_win(key, self.font_name, STYLES[style])
185
+ if path:
186
+ self.fonts[style] = ImageFont.truetype(path, self.font_size)
187
+ else:
188
+ if style == 'BOLDITALIC':
189
+ self.fonts[style] = self.fonts['BOLD']
190
+ else:
191
+ self.fonts[style] = self.fonts['NORMAL']
192
+ return
193
+ except FontNotFound as err:
194
+ lookuperror = err
195
+ finally:
196
+ _winreg.CloseKey(key)
197
+ except OSError:
198
+ pass
199
+ else:
200
+ # If we get here, we checked all registry keys and had no luck
201
+ # We can be in one of two situations now:
202
+ # * All key lookups failed. In this case lookuperror is None and we
203
+ # will raise a generic error
204
+ # * At least one lookup failed with a FontNotFound error. In this
205
+ # case, we will raise that as a more specific error
206
+ if lookuperror:
207
+ raise lookuperror
208
+ raise FontNotFound('Can\'t open Windows font registry key')
209
+
210
+ def get_char_size(self):
211
+ """
212
+ Get the character size.
213
+ """
214
+ return self.get_text_size('M')
215
+
216
+ def get_text_size(self, text):
217
+ """
218
+ Get the text size (width, height).
219
+ """
220
+ font = self.fonts['NORMAL']
221
+ if hasattr(font, 'getbbox'): # Pillow >= 9.2.0
222
+ return font.getbbox(text)[2:4]
223
+ else:
224
+ return font.getsize(text)
225
+
226
+ def get_font(self, bold, oblique):
227
+ """
228
+ Get the font based on bold and italic flags.
229
+ """
230
+ if bold and oblique:
231
+ if self.variable:
232
+ return self.get_style('BOLDITALIC')
233
+
234
+ return self.fonts['BOLDITALIC']
235
+ elif bold:
236
+ if self.variable:
237
+ return self.get_style('BOLD')
238
+
239
+ return self.fonts['BOLD']
240
+ elif oblique:
241
+ if self.variable:
242
+ return self.get_style('ITALIC')
243
+
244
+ return self.fonts['ITALIC']
245
+ else:
246
+ if self.variable:
247
+ return self.get_style('NORMAL')
248
+
249
+ return self.fonts['NORMAL']
250
+
251
+ def get_style(self, style):
252
+ """
253
+ Get the specified style of the font if it is a variable font.
254
+ If not found, return the normal font.
255
+ """
256
+ font = self.fonts[style]
257
+ for style_name in STYLES[style]:
258
+ try:
259
+ font.set_variation_by_name(style_name)
260
+ return font
261
+ except ValueError:
262
+ pass
263
+ except OSError:
264
+ return font
265
+
266
+ return font
267
+
268
+
269
+ class ImageFormatter(Formatter):
270
+ """
271
+ Create a PNG image from source code. This uses the Python Imaging Library to
272
+ generate a pixmap from the source code.
273
+
274
+ .. versionadded:: 0.10
275
+
276
+ Additional options accepted:
277
+
278
+ `image_format`
279
+ An image format to output to that is recognised by PIL, these include:
280
+
281
+ * "PNG" (default)
282
+ * "JPEG"
283
+ * "BMP"
284
+ * "GIF"
285
+
286
+ `line_pad`
287
+ The extra spacing (in pixels) between each line of text.
288
+
289
+ Default: 2
290
+
291
+ `font_name`
292
+ The font name to be used as the base font from which others, such as
293
+ bold and italic fonts will be generated. This really should be a
294
+ monospace font to look sane.
295
+ If a filename or a file-like object is specified, the user must
296
+ provide different styles of the font.
297
+
298
+ Default: "Courier New" on Windows, "Menlo" on Mac OS, and
299
+ "DejaVu Sans Mono" on \\*nix
300
+
301
+ `font_size`
302
+ The font size in points to be used.
303
+
304
+ Default: 14
305
+
306
+ `image_pad`
307
+ The padding, in pixels to be used at each edge of the resulting image.
308
+
309
+ Default: 10
310
+
311
+ `line_numbers`
312
+ Whether line numbers should be shown: True/False
313
+
314
+ Default: True
315
+
316
+ `line_number_start`
317
+ The line number of the first line.
318
+
319
+ Default: 1
320
+
321
+ `line_number_step`
322
+ The step used when printing line numbers.
323
+
324
+ Default: 1
325
+
326
+ `line_number_bg`
327
+ The background colour (in "#123456" format) of the line number bar, or
328
+ None to use the style background color.
329
+
330
+ Default: "#eed"
331
+
332
+ `line_number_fg`
333
+ The text color of the line numbers (in "#123456"-like format).
334
+
335
+ Default: "#886"
336
+
337
+ `line_number_chars`
338
+ The number of columns of line numbers allowable in the line number
339
+ margin.
340
+
341
+ Default: 2
342
+
343
+ `line_number_bold`
344
+ Whether line numbers will be bold: True/False
345
+
346
+ Default: False
347
+
348
+ `line_number_italic`
349
+ Whether line numbers will be italicized: True/False
350
+
351
+ Default: False
352
+
353
+ `line_number_separator`
354
+ Whether a line will be drawn between the line number area and the
355
+ source code area: True/False
356
+
357
+ Default: True
358
+
359
+ `line_number_pad`
360
+ The horizontal padding (in pixels) between the line number margin, and
361
+ the source code area.
362
+
363
+ Default: 6
364
+
365
+ `hl_lines`
366
+ Specify a list of lines to be highlighted.
367
+
368
+ .. versionadded:: 1.2
369
+
370
+ Default: empty list
371
+
372
+ `hl_color`
373
+ Specify the color for highlighting lines.
374
+
375
+ .. versionadded:: 1.2
376
+
377
+ Default: highlight color of the selected style
378
+ """
379
+
380
+ # Required by the pygments mapper
381
+ name = 'img'
382
+ aliases = ['img', 'IMG', 'png']
383
+ filenames = ['*.png']
384
+
385
+ unicodeoutput = False
386
+
387
+ default_image_format = 'png'
388
+
389
+ def __init__(self, **options):
390
+ """
391
+ See the class docstring for explanation of options.
392
+ """
393
+ if not pil_available:
394
+ raise PilNotAvailable(
395
+ 'Python Imaging Library is required for this formatter')
396
+ Formatter.__init__(self, **options)
397
+ self.encoding = 'latin1' # let pygments.format() do the right thing
398
+ # Read the style
399
+ self.styles = dict(self.style)
400
+ if self.style.background_color is None:
401
+ self.background_color = '#fff'
402
+ else:
403
+ self.background_color = self.style.background_color
404
+ # Image options
405
+ self.image_format = get_choice_opt(
406
+ options, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
407
+ self.default_image_format, normcase=True)
408
+ self.image_pad = get_int_opt(options, 'image_pad', 10)
409
+ self.line_pad = get_int_opt(options, 'line_pad', 2)
410
+ # The fonts
411
+ fontsize = get_int_opt(options, 'font_size', 14)
412
+ self.fonts = FontManager(options.get('font_name', ''), fontsize)
413
+ self.fontw, self.fonth = self.fonts.get_char_size()
414
+ # Line number options
415
+ self.line_number_fg = options.get('line_number_fg', '#886')
416
+ self.line_number_bg = options.get('line_number_bg', '#eed')
417
+ self.line_number_chars = get_int_opt(options,
418
+ 'line_number_chars', 2)
419
+ self.line_number_bold = get_bool_opt(options,
420
+ 'line_number_bold', False)
421
+ self.line_number_italic = get_bool_opt(options,
422
+ 'line_number_italic', False)
423
+ self.line_number_pad = get_int_opt(options, 'line_number_pad', 6)
424
+ self.line_numbers = get_bool_opt(options, 'line_numbers', True)
425
+ self.line_number_separator = get_bool_opt(options,
426
+ 'line_number_separator', True)
427
+ self.line_number_step = get_int_opt(options, 'line_number_step', 1)
428
+ self.line_number_start = get_int_opt(options, 'line_number_start', 1)
429
+ if self.line_numbers:
430
+ self.line_number_width = (self.fontw * self.line_number_chars +
431
+ self.line_number_pad * 2)
432
+ else:
433
+ self.line_number_width = 0
434
+ self.hl_lines = []
435
+ hl_lines_str = get_list_opt(options, 'hl_lines', [])
436
+ for line in hl_lines_str:
437
+ try:
438
+ self.hl_lines.append(int(line))
439
+ except ValueError:
440
+ pass
441
+ self.hl_color = options.get('hl_color',
442
+ self.style.highlight_color) or '#f90'
443
+ self.drawables = []
444
+
445
+ def get_style_defs(self, arg=''):
446
+ raise NotImplementedError('The -S option is meaningless for the image '
447
+ 'formatter. Use -O style=<stylename> instead.')
448
+
449
+ def _get_line_height(self):
450
+ """
451
+ Get the height of a line.
452
+ """
453
+ return self.fonth + self.line_pad
454
+
455
+ def _get_line_y(self, lineno):
456
+ """
457
+ Get the Y coordinate of a line number.
458
+ """
459
+ return lineno * self._get_line_height() + self.image_pad
460
+
461
+ def _get_char_width(self):
462
+ """
463
+ Get the width of a character.
464
+ """
465
+ return self.fontw
466
+
467
+ def _get_char_x(self, linelength):
468
+ """
469
+ Get the X coordinate of a character position.
470
+ """
471
+ return linelength + self.image_pad + self.line_number_width
472
+
473
+ def _get_text_pos(self, linelength, lineno):
474
+ """
475
+ Get the actual position for a character and line position.
476
+ """
477
+ return self._get_char_x(linelength), self._get_line_y(lineno)
478
+
479
+ def _get_linenumber_pos(self, lineno):
480
+ """
481
+ Get the actual position for the start of a line number.
482
+ """
483
+ return (self.image_pad, self._get_line_y(lineno))
484
+
485
+ def _get_text_color(self, style):
486
+ """
487
+ Get the correct color for the token from the style.
488
+ """
489
+ if style['color'] is not None:
490
+ fill = '#' + style['color']
491
+ else:
492
+ fill = '#000'
493
+ return fill
494
+
495
+ def _get_text_bg_color(self, style):
496
+ """
497
+ Get the correct background color for the token from the style.
498
+ """
499
+ if style['bgcolor'] is not None:
500
+ bg_color = '#' + style['bgcolor']
501
+ else:
502
+ bg_color = None
503
+ return bg_color
504
+
505
+ def _get_style_font(self, style):
506
+ """
507
+ Get the correct font for the style.
508
+ """
509
+ return self.fonts.get_font(style['bold'], style['italic'])
510
+
511
+ def _get_image_size(self, maxlinelength, maxlineno):
512
+ """
513
+ Get the required image size.
514
+ """
515
+ return (self._get_char_x(maxlinelength) + self.image_pad,
516
+ self._get_line_y(maxlineno + 0) + self.image_pad)
517
+
518
+ def _draw_linenumber(self, posno, lineno):
519
+ """
520
+ Remember a line number drawable to paint later.
521
+ """
522
+ self._draw_text(
523
+ self._get_linenumber_pos(posno),
524
+ str(lineno).rjust(self.line_number_chars),
525
+ font=self.fonts.get_font(self.line_number_bold,
526
+ self.line_number_italic),
527
+ text_fg=self.line_number_fg,
528
+ text_bg=None,
529
+ )
530
+
531
+ def _draw_text(self, pos, text, font, text_fg, text_bg):
532
+ """
533
+ Remember a single drawable tuple to paint later.
534
+ """
535
+ self.drawables.append((pos, text, font, text_fg, text_bg))
536
+
537
+ def _create_drawables(self, tokensource):
538
+ """
539
+ Create drawables for the token content.
540
+ """
541
+ lineno = charno = maxcharno = 0
542
+ maxlinelength = linelength = 0
543
+ for ttype, value in tokensource:
544
+ while ttype not in self.styles:
545
+ ttype = ttype.parent
546
+ style = self.styles[ttype]
547
+ # TODO: make sure tab expansion happens earlier in the chain. It
548
+ # really ought to be done on the input, as to do it right here is
549
+ # quite complex.
550
+ value = value.expandtabs(4)
551
+ lines = value.splitlines(True)
552
+ # print lines
553
+ for i, line in enumerate(lines):
554
+ temp = line.rstrip('\n')
555
+ if temp:
556
+ self._draw_text(
557
+ self._get_text_pos(linelength, lineno),
558
+ temp,
559
+ font = self._get_style_font(style),
560
+ text_fg = self._get_text_color(style),
561
+ text_bg = self._get_text_bg_color(style),
562
+ )
563
+ temp_width, _ = self.fonts.get_text_size(temp)
564
+ linelength += temp_width
565
+ maxlinelength = max(maxlinelength, linelength)
566
+ charno += len(temp)
567
+ maxcharno = max(maxcharno, charno)
568
+ if line.endswith('\n'):
569
+ # add a line for each extra line in the value
570
+ linelength = 0
571
+ charno = 0
572
+ lineno += 1
573
+ self.maxlinelength = maxlinelength
574
+ self.maxcharno = maxcharno
575
+ self.maxlineno = lineno
576
+
577
+ def _draw_line_numbers(self):
578
+ """
579
+ Create drawables for the line numbers.
580
+ """
581
+ if not self.line_numbers:
582
+ return
583
+ for p in range(self.maxlineno):
584
+ n = p + self.line_number_start
585
+ if (n % self.line_number_step) == 0:
586
+ self._draw_linenumber(p, n)
587
+
588
+ def _paint_line_number_bg(self, im):
589
+ """
590
+ Paint the line number background on the image.
591
+ """
592
+ if not self.line_numbers:
593
+ return
594
+ if self.line_number_fg is None:
595
+ return
596
+ draw = ImageDraw.Draw(im)
597
+ recth = im.size[-1]
598
+ rectw = self.image_pad + self.line_number_width - self.line_number_pad
599
+ draw.rectangle([(0, 0), (rectw, recth)],
600
+ fill=self.line_number_bg)
601
+ if self.line_number_separator:
602
+ draw.line([(rectw, 0), (rectw, recth)], fill=self.line_number_fg)
603
+ del draw
604
+
605
+ def format(self, tokensource, outfile):
606
+ """
607
+ Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
608
+ tuples and write it into ``outfile``.
609
+
610
+ This implementation calculates where it should draw each token on the
611
+ pixmap, then calculates the required pixmap size and draws the items.
612
+ """
613
+ self._create_drawables(tokensource)
614
+ self._draw_line_numbers()
615
+ im = Image.new(
616
+ 'RGB',
617
+ self._get_image_size(self.maxlinelength, self.maxlineno),
618
+ self.background_color
619
+ )
620
+ self._paint_line_number_bg(im)
621
+ draw = ImageDraw.Draw(im)
622
+ # Highlight
623
+ if self.hl_lines:
624
+ x = self.image_pad + self.line_number_width - self.line_number_pad + 1
625
+ recth = self._get_line_height()
626
+ rectw = im.size[0] - x
627
+ for linenumber in self.hl_lines:
628
+ y = self._get_line_y(linenumber - 1)
629
+ draw.rectangle([(x, y), (x + rectw, y + recth)],
630
+ fill=self.hl_color)
631
+ for pos, value, font, text_fg, text_bg in self.drawables:
632
+ if text_bg:
633
+ # see deprecations https://pillow.readthedocs.io/en/stable/releasenotes/9.2.0.html#font-size-and-offset-methods
634
+ if hasattr(draw, 'textsize'):
635
+ text_size = draw.textsize(text=value, font=font)
636
+ else:
637
+ text_size = font.getbbox(value)[2:]
638
+ draw.rectangle([pos[0], pos[1], pos[0] + text_size[0], pos[1] + text_size[1]], fill=text_bg)
639
+ draw.text(pos, value, font=font, fill=text_fg)
640
+ im.save(outfile, self.image_format.upper())
641
+
642
+
643
+ # Add one formatter per format, so that the "-f gif" option gives the correct result
644
+ # when used in pygmentize.
645
+
646
+ class GifImageFormatter(ImageFormatter):
647
+ """
648
+ Create a GIF image from source code. This uses the Python Imaging Library to
649
+ generate a pixmap from the source code.
650
+
651
+ .. versionadded:: 1.0
652
+ """
653
+
654
+ name = 'img_gif'
655
+ aliases = ['gif']
656
+ filenames = ['*.gif']
657
+ default_image_format = 'gif'
658
+
659
+
660
+ class JpgImageFormatter(ImageFormatter):
661
+ """
662
+ Create a JPEG image from source code. This uses the Python Imaging Library to
663
+ generate a pixmap from the source code.
664
+
665
+ .. versionadded:: 1.0
666
+ """
667
+
668
+ name = 'img_jpg'
669
+ aliases = ['jpg', 'jpeg']
670
+ filenames = ['*.jpg']
671
+ default_image_format = 'jpeg'
672
+
673
+
674
+ class BmpImageFormatter(ImageFormatter):
675
+ """
676
+ Create a bitmap image from source code. This uses the Python Imaging Library to
677
+ generate a pixmap from the source code.
678
+
679
+ .. versionadded:: 1.0
680
+ """
681
+
682
+ name = 'img_bmp'
683
+ aliases = ['bmp', 'bitmap']
684
+ filenames = ['*.bmp']
685
+ default_image_format = 'bmp'
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.irc
3
+ ~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for IRC output
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from pip._vendor.pygments.formatter import Formatter
12
+ from pip._vendor.pygments.token import Keyword, Name, Comment, String, Error, \
13
+ Number, Operator, Generic, Token, Whitespace
14
+ from pip._vendor.pygments.util import get_choice_opt
15
+
16
+
17
+ __all__ = ['IRCFormatter']
18
+
19
+
20
+ #: Map token types to a tuple of color values for light and dark
21
+ #: backgrounds.
22
+ IRC_COLORS = {
23
+ Token: ('', ''),
24
+
25
+ Whitespace: ('gray', 'brightblack'),
26
+ Comment: ('gray', 'brightblack'),
27
+ Comment.Preproc: ('cyan', 'brightcyan'),
28
+ Keyword: ('blue', 'brightblue'),
29
+ Keyword.Type: ('cyan', 'brightcyan'),
30
+ Operator.Word: ('magenta', 'brightcyan'),
31
+ Name.Builtin: ('cyan', 'brightcyan'),
32
+ Name.Function: ('green', 'brightgreen'),
33
+ Name.Namespace: ('_cyan_', '_brightcyan_'),
34
+ Name.Class: ('_green_', '_brightgreen_'),
35
+ Name.Exception: ('cyan', 'brightcyan'),
36
+ Name.Decorator: ('brightblack', 'gray'),
37
+ Name.Variable: ('red', 'brightred'),
38
+ Name.Constant: ('red', 'brightred'),
39
+ Name.Attribute: ('cyan', 'brightcyan'),
40
+ Name.Tag: ('brightblue', 'brightblue'),
41
+ String: ('yellow', 'yellow'),
42
+ Number: ('blue', 'brightblue'),
43
+
44
+ Generic.Deleted: ('brightred', 'brightred'),
45
+ Generic.Inserted: ('green', 'brightgreen'),
46
+ Generic.Heading: ('**', '**'),
47
+ Generic.Subheading: ('*magenta*', '*brightmagenta*'),
48
+ Generic.Error: ('brightred', 'brightred'),
49
+
50
+ Error: ('_brightred_', '_brightred_'),
51
+ }
52
+
53
+
54
+ IRC_COLOR_MAP = {
55
+ 'white': 0,
56
+ 'black': 1,
57
+ 'blue': 2,
58
+ 'brightgreen': 3,
59
+ 'brightred': 4,
60
+ 'yellow': 5,
61
+ 'magenta': 6,
62
+ 'orange': 7,
63
+ 'green': 7, #compat w/ ansi
64
+ 'brightyellow': 8,
65
+ 'lightgreen': 9,
66
+ 'brightcyan': 9, # compat w/ ansi
67
+ 'cyan': 10,
68
+ 'lightblue': 11,
69
+ 'red': 11, # compat w/ ansi
70
+ 'brightblue': 12,
71
+ 'brightmagenta': 13,
72
+ 'brightblack': 14,
73
+ 'gray': 15,
74
+ }
75
+
76
+ def ircformat(color, text):
77
+ if len(color) < 1:
78
+ return text
79
+ add = sub = ''
80
+ if '_' in color: # italic
81
+ add += '\x1D'
82
+ sub = '\x1D' + sub
83
+ color = color.strip('_')
84
+ if '*' in color: # bold
85
+ add += '\x02'
86
+ sub = '\x02' + sub
87
+ color = color.strip('*')
88
+ # underline (\x1F) not supported
89
+ # backgrounds (\x03FF,BB) not supported
90
+ if len(color) > 0: # actual color - may have issues with ircformat("red", "blah")+"10" type stuff
91
+ add += '\x03' + str(IRC_COLOR_MAP[color]).zfill(2)
92
+ sub = '\x03' + sub
93
+ return add + text + sub
94
+ return '<'+add+'>'+text+'</'+sub+'>'
95
+
96
+
97
+ class IRCFormatter(Formatter):
98
+ r"""
99
+ Format tokens with IRC color sequences
100
+
101
+ The `get_style_defs()` method doesn't do anything special since there is
102
+ no support for common styles.
103
+
104
+ Options accepted:
105
+
106
+ `bg`
107
+ Set to ``"light"`` or ``"dark"`` depending on the terminal's background
108
+ (default: ``"light"``).
109
+
110
+ `colorscheme`
111
+ A dictionary mapping token types to (lightbg, darkbg) color names or
112
+ ``None`` (default: ``None`` = use builtin colorscheme).
113
+
114
+ `linenos`
115
+ Set to ``True`` to have line numbers in the output as well
116
+ (default: ``False`` = no line numbers).
117
+ """
118
+ name = 'IRC'
119
+ aliases = ['irc', 'IRC']
120
+ filenames = []
121
+
122
+ def __init__(self, **options):
123
+ Formatter.__init__(self, **options)
124
+ self.darkbg = get_choice_opt(options, 'bg',
125
+ ['light', 'dark'], 'light') == 'dark'
126
+ self.colorscheme = options.get('colorscheme', None) or IRC_COLORS
127
+ self.linenos = options.get('linenos', False)
128
+ self._lineno = 0
129
+
130
+ def _write_lineno(self, outfile):
131
+ if self.linenos:
132
+ self._lineno += 1
133
+ outfile.write("%04d: " % self._lineno)
134
+
135
+ def format_unencoded(self, tokensource, outfile):
136
+ self._write_lineno(outfile)
137
+
138
+ for ttype, value in tokensource:
139
+ color = self.colorscheme.get(ttype)
140
+ while color is None:
141
+ ttype = ttype[:-1]
142
+ color = self.colorscheme.get(ttype)
143
+ if color:
144
+ color = color[self.darkbg]
145
+ spl = value.split('\n')
146
+ for line in spl[:-1]:
147
+ if line:
148
+ outfile.write(ircformat(color, line))
149
+ outfile.write('\n')
150
+ self._write_lineno(outfile)
151
+ if spl[-1]:
152
+ outfile.write(ircformat(color, spl[-1]))
153
+ else:
154
+ outfile.write(value)
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py ADDED
@@ -0,0 +1,518 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.latex
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for LaTeX fancyvrb output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from io import StringIO
12
+
13
+ from pip._vendor.pygments.formatter import Formatter
14
+ from pip._vendor.pygments.lexer import Lexer, do_insertions
15
+ from pip._vendor.pygments.token import Token, STANDARD_TYPES
16
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt
17
+
18
+
19
+ __all__ = ['LatexFormatter']
20
+
21
+
22
+ def escape_tex(text, commandprefix):
23
+ return text.replace('\\', '\x00'). \
24
+ replace('{', '\x01'). \
25
+ replace('}', '\x02'). \
26
+ replace('\x00', rf'\{commandprefix}Zbs{{}}'). \
27
+ replace('\x01', rf'\{commandprefix}Zob{{}}'). \
28
+ replace('\x02', rf'\{commandprefix}Zcb{{}}'). \
29
+ replace('^', rf'\{commandprefix}Zca{{}}'). \
30
+ replace('_', rf'\{commandprefix}Zus{{}}'). \
31
+ replace('&', rf'\{commandprefix}Zam{{}}'). \
32
+ replace('<', rf'\{commandprefix}Zlt{{}}'). \
33
+ replace('>', rf'\{commandprefix}Zgt{{}}'). \
34
+ replace('#', rf'\{commandprefix}Zsh{{}}'). \
35
+ replace('%', rf'\{commandprefix}Zpc{{}}'). \
36
+ replace('$', rf'\{commandprefix}Zdl{{}}'). \
37
+ replace('-', rf'\{commandprefix}Zhy{{}}'). \
38
+ replace("'", rf'\{commandprefix}Zsq{{}}'). \
39
+ replace('"', rf'\{commandprefix}Zdq{{}}'). \
40
+ replace('~', rf'\{commandprefix}Zti{{}}')
41
+
42
+
43
+ DOC_TEMPLATE = r'''
44
+ \documentclass{%(docclass)s}
45
+ \usepackage{fancyvrb}
46
+ \usepackage{color}
47
+ \usepackage[%(encoding)s]{inputenc}
48
+ %(preamble)s
49
+
50
+ %(styledefs)s
51
+
52
+ \begin{document}
53
+
54
+ \section*{%(title)s}
55
+
56
+ %(code)s
57
+ \end{document}
58
+ '''
59
+
60
+ ## Small explanation of the mess below :)
61
+ #
62
+ # The previous version of the LaTeX formatter just assigned a command to
63
+ # each token type defined in the current style. That obviously is
64
+ # problematic if the highlighted code is produced for a different style
65
+ # than the style commands themselves.
66
+ #
67
+ # This version works much like the HTML formatter which assigns multiple
68
+ # CSS classes to each <span> tag, from the most specific to the least
69
+ # specific token type, thus falling back to the parent token type if one
70
+ # is not defined. Here, the classes are there too and use the same short
71
+ # forms given in token.STANDARD_TYPES.
72
+ #
73
+ # Highlighted code now only uses one custom command, which by default is
74
+ # \PY and selectable by the commandprefix option (and in addition the
75
+ # escapes \PYZat, \PYZlb and \PYZrb which haven't been renamed for
76
+ # backwards compatibility purposes).
77
+ #
78
+ # \PY has two arguments: the classes, separated by +, and the text to
79
+ # render in that style. The classes are resolved into the respective
80
+ # style commands by magic, which serves to ignore unknown classes.
81
+ #
82
+ # The magic macros are:
83
+ # * \PY@it, \PY@bf, etc. are unconditionally wrapped around the text
84
+ # to render in \PY@do. Their definition determines the style.
85
+ # * \PY@reset resets \PY@it etc. to do nothing.
86
+ # * \PY@toks parses the list of classes, using magic inspired by the
87
+ # keyval package (but modified to use plusses instead of commas
88
+ # because fancyvrb redefines commas inside its environments).
89
+ # * \PY@tok processes one class, calling the \PY@tok@classname command
90
+ # if it exists.
91
+ # * \PY@tok@classname sets the \PY@it etc. to reflect the chosen style
92
+ # for its class.
93
+ # * \PY resets the style, parses the classnames and then calls \PY@do.
94
+ #
95
+ # Tip: to read this code, print it out in substituted form using e.g.
96
+ # >>> print STYLE_TEMPLATE % {'cp': 'PY'}
97
+
98
+ STYLE_TEMPLATE = r'''
99
+ \makeatletter
100
+ \def\%(cp)s@reset{\let\%(cp)s@it=\relax \let\%(cp)s@bf=\relax%%
101
+ \let\%(cp)s@ul=\relax \let\%(cp)s@tc=\relax%%
102
+ \let\%(cp)s@bc=\relax \let\%(cp)s@ff=\relax}
103
+ \def\%(cp)s@tok#1{\csname %(cp)s@tok@#1\endcsname}
104
+ \def\%(cp)s@toks#1+{\ifx\relax#1\empty\else%%
105
+ \%(cp)s@tok{#1}\expandafter\%(cp)s@toks\fi}
106
+ \def\%(cp)s@do#1{\%(cp)s@bc{\%(cp)s@tc{\%(cp)s@ul{%%
107
+ \%(cp)s@it{\%(cp)s@bf{\%(cp)s@ff{#1}}}}}}}
108
+ \def\%(cp)s#1#2{\%(cp)s@reset\%(cp)s@toks#1+\relax+\%(cp)s@do{#2}}
109
+
110
+ %(styles)s
111
+
112
+ \def\%(cp)sZbs{\char`\\}
113
+ \def\%(cp)sZus{\char`\_}
114
+ \def\%(cp)sZob{\char`\{}
115
+ \def\%(cp)sZcb{\char`\}}
116
+ \def\%(cp)sZca{\char`\^}
117
+ \def\%(cp)sZam{\char`\&}
118
+ \def\%(cp)sZlt{\char`\<}
119
+ \def\%(cp)sZgt{\char`\>}
120
+ \def\%(cp)sZsh{\char`\#}
121
+ \def\%(cp)sZpc{\char`\%%}
122
+ \def\%(cp)sZdl{\char`\$}
123
+ \def\%(cp)sZhy{\char`\-}
124
+ \def\%(cp)sZsq{\char`\'}
125
+ \def\%(cp)sZdq{\char`\"}
126
+ \def\%(cp)sZti{\char`\~}
127
+ %% for compatibility with earlier versions
128
+ \def\%(cp)sZat{@}
129
+ \def\%(cp)sZlb{[}
130
+ \def\%(cp)sZrb{]}
131
+ \makeatother
132
+ '''
133
+
134
+
135
+ def _get_ttype_name(ttype):
136
+ fname = STANDARD_TYPES.get(ttype)
137
+ if fname:
138
+ return fname
139
+ aname = ''
140
+ while fname is None:
141
+ aname = ttype[-1] + aname
142
+ ttype = ttype.parent
143
+ fname = STANDARD_TYPES.get(ttype)
144
+ return fname + aname
145
+
146
+
147
+ class LatexFormatter(Formatter):
148
+ r"""
149
+ Format tokens as LaTeX code. This needs the `fancyvrb` and `color`
150
+ standard packages.
151
+
152
+ Without the `full` option, code is formatted as one ``Verbatim``
153
+ environment, like this:
154
+
155
+ .. sourcecode:: latex
156
+
157
+ \begin{Verbatim}[commandchars=\\\{\}]
158
+ \PY{k}{def }\PY{n+nf}{foo}(\PY{n}{bar}):
159
+ \PY{k}{pass}
160
+ \end{Verbatim}
161
+
162
+ Wrapping can be disabled using the `nowrap` option.
163
+
164
+ The special command used here (``\PY``) and all the other macros it needs
165
+ are output by the `get_style_defs` method.
166
+
167
+ With the `full` option, a complete LaTeX document is output, including
168
+ the command definitions in the preamble.
169
+
170
+ The `get_style_defs()` method of a `LatexFormatter` returns a string
171
+ containing ``\def`` commands defining the macros needed inside the
172
+ ``Verbatim`` environments.
173
+
174
+ Additional options accepted:
175
+
176
+ `nowrap`
177
+ If set to ``True``, don't wrap the tokens at all, not even inside a
178
+ ``\begin{Verbatim}`` environment. This disables most other options
179
+ (default: ``False``).
180
+
181
+ `style`
182
+ The style to use, can be a string or a Style subclass (default:
183
+ ``'default'``).
184
+
185
+ `full`
186
+ Tells the formatter to output a "full" document, i.e. a complete
187
+ self-contained document (default: ``False``).
188
+
189
+ `title`
190
+ If `full` is true, the title that should be used to caption the
191
+ document (default: ``''``).
192
+
193
+ `docclass`
194
+ If the `full` option is enabled, this is the document class to use
195
+ (default: ``'article'``).
196
+
197
+ `preamble`
198
+ If the `full` option is enabled, this can be further preamble commands,
199
+ e.g. ``\usepackage`` (default: ``''``).
200
+
201
+ `linenos`
202
+ If set to ``True``, output line numbers (default: ``False``).
203
+
204
+ `linenostart`
205
+ The line number for the first line (default: ``1``).
206
+
207
+ `linenostep`
208
+ If set to a number n > 1, only every nth line number is printed.
209
+
210
+ `verboptions`
211
+ Additional options given to the Verbatim environment (see the *fancyvrb*
212
+ docs for possible values) (default: ``''``).
213
+
214
+ `commandprefix`
215
+ The LaTeX commands used to produce colored output are constructed
216
+ using this prefix and some letters (default: ``'PY'``).
217
+
218
+ .. versionadded:: 0.7
219
+ .. versionchanged:: 0.10
220
+ The default is now ``'PY'`` instead of ``'C'``.
221
+
222
+ `texcomments`
223
+ If set to ``True``, enables LaTeX comment lines. That is, LaTex markup
224
+ in comment tokens is not escaped so that LaTeX can render it (default:
225
+ ``False``).
226
+
227
+ .. versionadded:: 1.2
228
+
229
+ `mathescape`
230
+ If set to ``True``, enables LaTeX math mode escape in comments. That
231
+ is, ``'$...$'`` inside a comment will trigger math mode (default:
232
+ ``False``).
233
+
234
+ .. versionadded:: 1.2
235
+
236
+ `escapeinside`
237
+ If set to a string of length 2, enables escaping to LaTeX. Text
238
+ delimited by these 2 characters is read as LaTeX code and
239
+ typeset accordingly. It has no effect in string literals. It has
240
+ no effect in comments if `texcomments` or `mathescape` is
241
+ set. (default: ``''``).
242
+
243
+ .. versionadded:: 2.0
244
+
245
+ `envname`
246
+ Allows you to pick an alternative environment name replacing Verbatim.
247
+ The alternate environment still has to support Verbatim's option syntax.
248
+ (default: ``'Verbatim'``).
249
+
250
+ .. versionadded:: 2.0
251
+ """
252
+ name = 'LaTeX'
253
+ aliases = ['latex', 'tex']
254
+ filenames = ['*.tex']
255
+
256
+ def __init__(self, **options):
257
+ Formatter.__init__(self, **options)
258
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
259
+ self.docclass = options.get('docclass', 'article')
260
+ self.preamble = options.get('preamble', '')
261
+ self.linenos = get_bool_opt(options, 'linenos', False)
262
+ self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
263
+ self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
264
+ self.verboptions = options.get('verboptions', '')
265
+ self.nobackground = get_bool_opt(options, 'nobackground', False)
266
+ self.commandprefix = options.get('commandprefix', 'PY')
267
+ self.texcomments = get_bool_opt(options, 'texcomments', False)
268
+ self.mathescape = get_bool_opt(options, 'mathescape', False)
269
+ self.escapeinside = options.get('escapeinside', '')
270
+ if len(self.escapeinside) == 2:
271
+ self.left = self.escapeinside[0]
272
+ self.right = self.escapeinside[1]
273
+ else:
274
+ self.escapeinside = ''
275
+ self.envname = options.get('envname', 'Verbatim')
276
+
277
+ self._create_stylesheet()
278
+
279
+ def _create_stylesheet(self):
280
+ t2n = self.ttype2name = {Token: ''}
281
+ c2d = self.cmd2def = {}
282
+ cp = self.commandprefix
283
+
284
+ def rgbcolor(col):
285
+ if col:
286
+ return ','.join(['%.2f' % (int(col[i] + col[i + 1], 16) / 255.0)
287
+ for i in (0, 2, 4)])
288
+ else:
289
+ return '1,1,1'
290
+
291
+ for ttype, ndef in self.style:
292
+ name = _get_ttype_name(ttype)
293
+ cmndef = ''
294
+ if ndef['bold']:
295
+ cmndef += r'\let\$$@bf=\textbf'
296
+ if ndef['italic']:
297
+ cmndef += r'\let\$$@it=\textit'
298
+ if ndef['underline']:
299
+ cmndef += r'\let\$$@ul=\underline'
300
+ if ndef['roman']:
301
+ cmndef += r'\let\$$@ff=\textrm'
302
+ if ndef['sans']:
303
+ cmndef += r'\let\$$@ff=\textsf'
304
+ if ndef['mono']:
305
+ cmndef += r'\let\$$@ff=\textsf'
306
+ if ndef['color']:
307
+ cmndef += (r'\def\$$@tc##1{{\textcolor[rgb]{{{}}}{{##1}}}}'.format(rgbcolor(ndef['color'])))
308
+ if ndef['border']:
309
+ cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{\string -\fboxrule}}'
310
+ r'\fcolorbox[rgb]{{{}}}{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['border']),
311
+ rgbcolor(ndef['bgcolor'])))
312
+ elif ndef['bgcolor']:
313
+ cmndef += (r'\def\$$@bc##1{{{{\setlength{{\fboxsep}}{{0pt}}'
314
+ r'\colorbox[rgb]{{{}}}{{\strut ##1}}}}}}'.format(rgbcolor(ndef['bgcolor'])))
315
+ if cmndef == '':
316
+ continue
317
+ cmndef = cmndef.replace('$$', cp)
318
+ t2n[ttype] = name
319
+ c2d[name] = cmndef
320
+
321
+ def get_style_defs(self, arg=''):
322
+ """
323
+ Return the command sequences needed to define the commands
324
+ used to format text in the verbatim environment. ``arg`` is ignored.
325
+ """
326
+ cp = self.commandprefix
327
+ styles = []
328
+ for name, definition in self.cmd2def.items():
329
+ styles.append(rf'\@namedef{{{cp}@tok@{name}}}{{{definition}}}')
330
+ return STYLE_TEMPLATE % {'cp': self.commandprefix,
331
+ 'styles': '\n'.join(styles)}
332
+
333
+ def format_unencoded(self, tokensource, outfile):
334
+ # TODO: add support for background colors
335
+ t2n = self.ttype2name
336
+ cp = self.commandprefix
337
+
338
+ if self.full:
339
+ realoutfile = outfile
340
+ outfile = StringIO()
341
+
342
+ if not self.nowrap:
343
+ outfile.write('\\begin{' + self.envname + '}[commandchars=\\\\\\{\\}')
344
+ if self.linenos:
345
+ start, step = self.linenostart, self.linenostep
346
+ outfile.write(',numbers=left' +
347
+ (start and ',firstnumber=%d' % start or '') +
348
+ (step and ',stepnumber=%d' % step or ''))
349
+ if self.mathescape or self.texcomments or self.escapeinside:
350
+ outfile.write(',codes={\\catcode`\\$=3\\catcode`\\^=7'
351
+ '\\catcode`\\_=8\\relax}')
352
+ if self.verboptions:
353
+ outfile.write(',' + self.verboptions)
354
+ outfile.write(']\n')
355
+
356
+ for ttype, value in tokensource:
357
+ if ttype in Token.Comment:
358
+ if self.texcomments:
359
+ # Try to guess comment starting lexeme and escape it ...
360
+ start = value[0:1]
361
+ for i in range(1, len(value)):
362
+ if start[0] != value[i]:
363
+ break
364
+ start += value[i]
365
+
366
+ value = value[len(start):]
367
+ start = escape_tex(start, cp)
368
+
369
+ # ... but do not escape inside comment.
370
+ value = start + value
371
+ elif self.mathescape:
372
+ # Only escape parts not inside a math environment.
373
+ parts = value.split('$')
374
+ in_math = False
375
+ for i, part in enumerate(parts):
376
+ if not in_math:
377
+ parts[i] = escape_tex(part, cp)
378
+ in_math = not in_math
379
+ value = '$'.join(parts)
380
+ elif self.escapeinside:
381
+ text = value
382
+ value = ''
383
+ while text:
384
+ a, sep1, text = text.partition(self.left)
385
+ if sep1:
386
+ b, sep2, text = text.partition(self.right)
387
+ if sep2:
388
+ value += escape_tex(a, cp) + b
389
+ else:
390
+ value += escape_tex(a + sep1 + b, cp)
391
+ else:
392
+ value += escape_tex(a, cp)
393
+ else:
394
+ value = escape_tex(value, cp)
395
+ elif ttype not in Token.Escape:
396
+ value = escape_tex(value, cp)
397
+ styles = []
398
+ while ttype is not Token:
399
+ try:
400
+ styles.append(t2n[ttype])
401
+ except KeyError:
402
+ # not in current style
403
+ styles.append(_get_ttype_name(ttype))
404
+ ttype = ttype.parent
405
+ styleval = '+'.join(reversed(styles))
406
+ if styleval:
407
+ spl = value.split('\n')
408
+ for line in spl[:-1]:
409
+ if line:
410
+ outfile.write(f"\\{cp}{{{styleval}}}{{{line}}}")
411
+ outfile.write('\n')
412
+ if spl[-1]:
413
+ outfile.write(f"\\{cp}{{{styleval}}}{{{spl[-1]}}}")
414
+ else:
415
+ outfile.write(value)
416
+
417
+ if not self.nowrap:
418
+ outfile.write('\\end{' + self.envname + '}\n')
419
+
420
+ if self.full:
421
+ encoding = self.encoding or 'utf8'
422
+ # map known existings encodings from LaTeX distribution
423
+ encoding = {
424
+ 'utf_8': 'utf8',
425
+ 'latin_1': 'latin1',
426
+ 'iso_8859_1': 'latin1',
427
+ }.get(encoding.replace('-', '_'), encoding)
428
+ realoutfile.write(DOC_TEMPLATE %
429
+ dict(docclass = self.docclass,
430
+ preamble = self.preamble,
431
+ title = self.title,
432
+ encoding = encoding,
433
+ styledefs = self.get_style_defs(),
434
+ code = outfile.getvalue()))
435
+
436
+
437
+ class LatexEmbeddedLexer(Lexer):
438
+ """
439
+ This lexer takes one lexer as argument, the lexer for the language
440
+ being formatted, and the left and right delimiters for escaped text.
441
+
442
+ First everything is scanned using the language lexer to obtain
443
+ strings and comments. All other consecutive tokens are merged and
444
+ the resulting text is scanned for escaped segments, which are given
445
+ the Token.Escape type. Finally text that is not escaped is scanned
446
+ again with the language lexer.
447
+ """
448
+ def __init__(self, left, right, lang, **options):
449
+ self.left = left
450
+ self.right = right
451
+ self.lang = lang
452
+ Lexer.__init__(self, **options)
453
+
454
+ def get_tokens_unprocessed(self, text):
455
+ # find and remove all the escape tokens (replace with an empty string)
456
+ # this is very similar to DelegatingLexer.get_tokens_unprocessed.
457
+ buffered = ''
458
+ insertions = []
459
+ insertion_buf = []
460
+ for i, t, v in self._find_safe_escape_tokens(text):
461
+ if t is None:
462
+ if insertion_buf:
463
+ insertions.append((len(buffered), insertion_buf))
464
+ insertion_buf = []
465
+ buffered += v
466
+ else:
467
+ insertion_buf.append((i, t, v))
468
+ if insertion_buf:
469
+ insertions.append((len(buffered), insertion_buf))
470
+ return do_insertions(insertions,
471
+ self.lang.get_tokens_unprocessed(buffered))
472
+
473
+ def _find_safe_escape_tokens(self, text):
474
+ """ find escape tokens that are not in strings or comments """
475
+ for i, t, v in self._filter_to(
476
+ self.lang.get_tokens_unprocessed(text),
477
+ lambda t: t in Token.Comment or t in Token.String
478
+ ):
479
+ if t is None:
480
+ for i2, t2, v2 in self._find_escape_tokens(v):
481
+ yield i + i2, t2, v2
482
+ else:
483
+ yield i, None, v
484
+
485
+ def _filter_to(self, it, pred):
486
+ """ Keep only the tokens that match `pred`, merge the others together """
487
+ buf = ''
488
+ idx = 0
489
+ for i, t, v in it:
490
+ if pred(t):
491
+ if buf:
492
+ yield idx, None, buf
493
+ buf = ''
494
+ yield i, t, v
495
+ else:
496
+ if not buf:
497
+ idx = i
498
+ buf += v
499
+ if buf:
500
+ yield idx, None, buf
501
+
502
+ def _find_escape_tokens(self, text):
503
+ """ Find escape tokens within text, give token=None otherwise """
504
+ index = 0
505
+ while text:
506
+ a, sep1, text = text.partition(self.left)
507
+ if a:
508
+ yield index, None, a
509
+ index += len(a)
510
+ if sep1:
511
+ b, sep2, text = text.partition(self.right)
512
+ if sep2:
513
+ yield index + len(sep1), Token.Escape, b
514
+ index += len(sep1) + len(b) + len(sep2)
515
+ else:
516
+ yield index, Token.Error, sep1
517
+ index += len(sep1)
518
+ text = b
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.other
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Other formatters: NullFormatter, RawTokenFormatter.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from pip._vendor.pygments.formatter import Formatter
12
+ from pip._vendor.pygments.util import get_choice_opt
13
+ from pip._vendor.pygments.token import Token
14
+ from pip._vendor.pygments.console import colorize
15
+
16
+ __all__ = ['NullFormatter', 'RawTokenFormatter', 'TestcaseFormatter']
17
+
18
+
19
+ class NullFormatter(Formatter):
20
+ """
21
+ Output the text unchanged without any formatting.
22
+ """
23
+ name = 'Text only'
24
+ aliases = ['text', 'null']
25
+ filenames = ['*.txt']
26
+
27
+ def format(self, tokensource, outfile):
28
+ enc = self.encoding
29
+ for ttype, value in tokensource:
30
+ if enc:
31
+ outfile.write(value.encode(enc))
32
+ else:
33
+ outfile.write(value)
34
+
35
+
36
+ class RawTokenFormatter(Formatter):
37
+ r"""
38
+ Format tokens as a raw representation for storing token streams.
39
+
40
+ The format is ``tokentype<TAB>repr(tokenstring)\n``. The output can later
41
+ be converted to a token stream with the `RawTokenLexer`, described in the
42
+ :doc:`lexer list <lexers>`.
43
+
44
+ Only two options are accepted:
45
+
46
+ `compress`
47
+ If set to ``'gz'`` or ``'bz2'``, compress the output with the given
48
+ compression algorithm after encoding (default: ``''``).
49
+ `error_color`
50
+ If set to a color name, highlight error tokens using that color. If
51
+ set but with no value, defaults to ``'red'``.
52
+
53
+ .. versionadded:: 0.11
54
+
55
+ """
56
+ name = 'Raw tokens'
57
+ aliases = ['raw', 'tokens']
58
+ filenames = ['*.raw']
59
+
60
+ unicodeoutput = False
61
+
62
+ def __init__(self, **options):
63
+ Formatter.__init__(self, **options)
64
+ # We ignore self.encoding if it is set, since it gets set for lexer
65
+ # and formatter if given with -Oencoding on the command line.
66
+ # The RawTokenFormatter outputs only ASCII. Override here.
67
+ self.encoding = 'ascii' # let pygments.format() do the right thing
68
+ self.compress = get_choice_opt(options, 'compress',
69
+ ['', 'none', 'gz', 'bz2'], '')
70
+ self.error_color = options.get('error_color', None)
71
+ if self.error_color is True:
72
+ self.error_color = 'red'
73
+ if self.error_color is not None:
74
+ try:
75
+ colorize(self.error_color, '')
76
+ except KeyError:
77
+ raise ValueError(f"Invalid color {self.error_color!r} specified")
78
+
79
+ def format(self, tokensource, outfile):
80
+ try:
81
+ outfile.write(b'')
82
+ except TypeError:
83
+ raise TypeError('The raw tokens formatter needs a binary '
84
+ 'output file')
85
+ if self.compress == 'gz':
86
+ import gzip
87
+ outfile = gzip.GzipFile('', 'wb', 9, outfile)
88
+
89
+ write = outfile.write
90
+ flush = outfile.close
91
+ elif self.compress == 'bz2':
92
+ import bz2
93
+ compressor = bz2.BZ2Compressor(9)
94
+
95
+ def write(text):
96
+ outfile.write(compressor.compress(text))
97
+
98
+ def flush():
99
+ outfile.write(compressor.flush())
100
+ outfile.flush()
101
+ else:
102
+ write = outfile.write
103
+ flush = outfile.flush
104
+
105
+ if self.error_color:
106
+ for ttype, value in tokensource:
107
+ line = b"%r\t%r\n" % (ttype, value)
108
+ if ttype is Token.Error:
109
+ write(colorize(self.error_color, line))
110
+ else:
111
+ write(line)
112
+ else:
113
+ for ttype, value in tokensource:
114
+ write(b"%r\t%r\n" % (ttype, value))
115
+ flush()
116
+
117
+
118
+ TESTCASE_BEFORE = '''\
119
+ def testNeedsName(lexer):
120
+ fragment = %r
121
+ tokens = [
122
+ '''
123
+ TESTCASE_AFTER = '''\
124
+ ]
125
+ assert list(lexer.get_tokens(fragment)) == tokens
126
+ '''
127
+
128
+
129
+ class TestcaseFormatter(Formatter):
130
+ """
131
+ Format tokens as appropriate for a new testcase.
132
+
133
+ .. versionadded:: 2.0
134
+ """
135
+ name = 'Testcase'
136
+ aliases = ['testcase']
137
+
138
+ def __init__(self, **options):
139
+ Formatter.__init__(self, **options)
140
+ if self.encoding is not None and self.encoding != 'utf-8':
141
+ raise ValueError("Only None and utf-8 are allowed encodings.")
142
+
143
+ def format(self, tokensource, outfile):
144
+ indentation = ' ' * 12
145
+ rawbuf = []
146
+ outbuf = []
147
+ for ttype, value in tokensource:
148
+ rawbuf.append(value)
149
+ outbuf.append(f'{indentation}({ttype}, {value!r}),\n')
150
+
151
+ before = TESTCASE_BEFORE % (''.join(rawbuf),)
152
+ during = ''.join(outbuf)
153
+ after = TESTCASE_AFTER
154
+ if self.encoding is None:
155
+ outfile.write(before + during + after)
156
+ else:
157
+ outfile.write(before.encode('utf-8'))
158
+ outfile.write(during.encode('utf-8'))
159
+ outfile.write(after.encode('utf-8'))
160
+ outfile.flush()
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.pangomarkup
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for Pango markup output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from pip._vendor.pygments.formatter import Formatter
12
+
13
+
14
+ __all__ = ['PangoMarkupFormatter']
15
+
16
+
17
+ _escape_table = {
18
+ ord('&'): '&amp;',
19
+ ord('<'): '&lt;',
20
+ }
21
+
22
+
23
+ def escape_special_chars(text, table=_escape_table):
24
+ """Escape & and < for Pango Markup."""
25
+ return text.translate(table)
26
+
27
+
28
+ class PangoMarkupFormatter(Formatter):
29
+ """
30
+ Format tokens as Pango Markup code. It can then be rendered to an SVG.
31
+
32
+ .. versionadded:: 2.9
33
+ """
34
+
35
+ name = 'Pango Markup'
36
+ aliases = ['pango', 'pangomarkup']
37
+ filenames = []
38
+
39
+ def __init__(self, **options):
40
+ Formatter.__init__(self, **options)
41
+
42
+ self.styles = {}
43
+
44
+ for token, style in self.style:
45
+ start = ''
46
+ end = ''
47
+ if style['color']:
48
+ start += '<span fgcolor="#{}">'.format(style['color'])
49
+ end = '</span>' + end
50
+ if style['bold']:
51
+ start += '<b>'
52
+ end = '</b>' + end
53
+ if style['italic']:
54
+ start += '<i>'
55
+ end = '</i>' + end
56
+ if style['underline']:
57
+ start += '<u>'
58
+ end = '</u>' + end
59
+ self.styles[token] = (start, end)
60
+
61
+ def format_unencoded(self, tokensource, outfile):
62
+ lastval = ''
63
+ lasttype = None
64
+
65
+ outfile.write('<tt>')
66
+
67
+ for ttype, value in tokensource:
68
+ while ttype not in self.styles:
69
+ ttype = ttype.parent
70
+ if ttype == lasttype:
71
+ lastval += escape_special_chars(value)
72
+ else:
73
+ if lastval:
74
+ stylebegin, styleend = self.styles[lasttype]
75
+ outfile.write(stylebegin + lastval + styleend)
76
+ lastval = escape_special_chars(value)
77
+ lasttype = ttype
78
+
79
+ if lastval:
80
+ stylebegin, styleend = self.styles[lasttype]
81
+ outfile.write(stylebegin + lastval + styleend)
82
+
83
+ outfile.write('</tt>')
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.rtf
3
+ ~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ A formatter that generates RTF files.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from collections import OrderedDict
12
+ from pip._vendor.pygments.formatter import Formatter
13
+ from pip._vendor.pygments.style import _ansimap
14
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt, get_list_opt, surrogatepair
15
+
16
+
17
+ __all__ = ['RtfFormatter']
18
+
19
+
20
+ class RtfFormatter(Formatter):
21
+ """
22
+ Format tokens as RTF markup. This formatter automatically outputs full RTF
23
+ documents with color information and other useful stuff. Perfect for Copy and
24
+ Paste into Microsoft(R) Word(R) documents.
25
+
26
+ Please note that ``encoding`` and ``outencoding`` options are ignored.
27
+ The RTF format is ASCII natively, but handles unicode characters correctly
28
+ thanks to escape sequences.
29
+
30
+ .. versionadded:: 0.6
31
+
32
+ Additional options accepted:
33
+
34
+ `style`
35
+ The style to use, can be a string or a Style subclass (default:
36
+ ``'default'``).
37
+
38
+ `fontface`
39
+ The used font family, for example ``Bitstream Vera Sans``. Defaults to
40
+ some generic font which is supposed to have fixed width.
41
+
42
+ `fontsize`
43
+ Size of the font used. Size is specified in half points. The
44
+ default is 24 half-points, giving a size 12 font.
45
+
46
+ .. versionadded:: 2.0
47
+
48
+ `linenos`
49
+ Turn on line numbering (default: ``False``).
50
+
51
+ .. versionadded:: 2.18
52
+
53
+ `lineno_fontsize`
54
+ Font size for line numbers. Size is specified in half points
55
+ (default: `fontsize`).
56
+
57
+ .. versionadded:: 2.18
58
+
59
+ `lineno_padding`
60
+ Number of spaces between the (inline) line numbers and the
61
+ source code (default: ``2``).
62
+
63
+ .. versionadded:: 2.18
64
+
65
+ `linenostart`
66
+ The line number for the first line (default: ``1``).
67
+
68
+ .. versionadded:: 2.18
69
+
70
+ `linenostep`
71
+ If set to a number n > 1, only every nth line number is printed.
72
+
73
+ .. versionadded:: 2.18
74
+
75
+ `lineno_color`
76
+ Color for line numbers specified as a hex triplet, e.g. ``'5e5e5e'``.
77
+ Defaults to the style's line number color if it is a hex triplet,
78
+ otherwise ansi bright black.
79
+
80
+ .. versionadded:: 2.18
81
+
82
+ `hl_lines`
83
+ Specify a list of lines to be highlighted, as line numbers separated by
84
+ spaces, e.g. ``'3 7 8'``. The line numbers are relative to the input
85
+ (i.e. the first line is line 1) unless `hl_linenostart` is set.
86
+
87
+ .. versionadded:: 2.18
88
+
89
+ `hl_color`
90
+ Color for highlighting the lines specified in `hl_lines`, specified as
91
+ a hex triplet (default: style's `highlight_color`).
92
+
93
+ .. versionadded:: 2.18
94
+
95
+ `hl_linenostart`
96
+ If set to ``True`` line numbers in `hl_lines` are specified
97
+ relative to `linenostart` (default ``False``).
98
+
99
+ .. versionadded:: 2.18
100
+ """
101
+ name = 'RTF'
102
+ aliases = ['rtf']
103
+ filenames = ['*.rtf']
104
+
105
+ def __init__(self, **options):
106
+ r"""
107
+ Additional options accepted:
108
+
109
+ ``fontface``
110
+ Name of the font used. Could for example be ``'Courier New'``
111
+ to further specify the default which is ``'\fmodern'``. The RTF
112
+ specification claims that ``\fmodern`` are "Fixed-pitch serif
113
+ and sans serif fonts". Hope every RTF implementation thinks
114
+ the same about modern...
115
+
116
+ """
117
+ Formatter.__init__(self, **options)
118
+ self.fontface = options.get('fontface') or ''
119
+ self.fontsize = get_int_opt(options, 'fontsize', 0)
120
+ self.linenos = get_bool_opt(options, 'linenos', False)
121
+ self.lineno_fontsize = get_int_opt(options, 'lineno_fontsize',
122
+ self.fontsize)
123
+ self.lineno_padding = get_int_opt(options, 'lineno_padding', 2)
124
+ self.linenostart = abs(get_int_opt(options, 'linenostart', 1))
125
+ self.linenostep = abs(get_int_opt(options, 'linenostep', 1))
126
+ self.hl_linenostart = get_bool_opt(options, 'hl_linenostart', False)
127
+
128
+ self.hl_color = options.get('hl_color', '')
129
+ if not self.hl_color:
130
+ self.hl_color = self.style.highlight_color
131
+
132
+ self.hl_lines = []
133
+ for lineno in get_list_opt(options, 'hl_lines', []):
134
+ try:
135
+ lineno = int(lineno)
136
+ if self.hl_linenostart:
137
+ lineno = lineno - self.linenostart + 1
138
+ self.hl_lines.append(lineno)
139
+ except ValueError:
140
+ pass
141
+
142
+ self.lineno_color = options.get('lineno_color', '')
143
+ if not self.lineno_color:
144
+ if self.style.line_number_color == 'inherit':
145
+ # style color is the css value 'inherit'
146
+ # default to ansi bright-black
147
+ self.lineno_color = _ansimap['ansibrightblack']
148
+ else:
149
+ # style color is assumed to be a hex triplet as other
150
+ # colors in pygments/style.py
151
+ self.lineno_color = self.style.line_number_color
152
+
153
+ self.color_mapping = self._create_color_mapping()
154
+
155
+ def _escape(self, text):
156
+ return text.replace('\\', '\\\\') \
157
+ .replace('{', '\\{') \
158
+ .replace('}', '\\}')
159
+
160
+ def _escape_text(self, text):
161
+ # empty strings, should give a small performance improvement
162
+ if not text:
163
+ return ''
164
+
165
+ # escape text
166
+ text = self._escape(text)
167
+
168
+ buf = []
169
+ for c in text:
170
+ cn = ord(c)
171
+ if cn < (2**7):
172
+ # ASCII character
173
+ buf.append(str(c))
174
+ elif (2**7) <= cn < (2**16):
175
+ # single unicode escape sequence
176
+ buf.append('{\\u%d}' % cn)
177
+ elif (2**16) <= cn:
178
+ # RTF limits unicode to 16 bits.
179
+ # Force surrogate pairs
180
+ buf.append('{\\u%d}{\\u%d}' % surrogatepair(cn))
181
+
182
+ return ''.join(buf).replace('\n', '\\par')
183
+
184
+ @staticmethod
185
+ def hex_to_rtf_color(hex_color):
186
+ if hex_color[0] == "#":
187
+ hex_color = hex_color[1:]
188
+
189
+ return '\\red%d\\green%d\\blue%d;' % (
190
+ int(hex_color[0:2], 16),
191
+ int(hex_color[2:4], 16),
192
+ int(hex_color[4:6], 16)
193
+ )
194
+
195
+ def _split_tokens_on_newlines(self, tokensource):
196
+ """
197
+ Split tokens containing newline characters into multiple token
198
+ each representing a line of the input file. Needed for numbering
199
+ lines of e.g. multiline comments.
200
+ """
201
+ for ttype, value in tokensource:
202
+ if value == '\n':
203
+ yield (ttype, value)
204
+ elif "\n" in value:
205
+ lines = value.split("\n")
206
+ for line in lines[:-1]:
207
+ yield (ttype, line+"\n")
208
+ if lines[-1]:
209
+ yield (ttype, lines[-1])
210
+ else:
211
+ yield (ttype, value)
212
+
213
+ def _create_color_mapping(self):
214
+ """
215
+ Create a mapping of style hex colors to index/offset in
216
+ the RTF color table.
217
+ """
218
+ color_mapping = OrderedDict()
219
+ offset = 1
220
+
221
+ if self.linenos:
222
+ color_mapping[self.lineno_color] = offset
223
+ offset += 1
224
+
225
+ if self.hl_lines:
226
+ color_mapping[self.hl_color] = offset
227
+ offset += 1
228
+
229
+ for _, style in self.style:
230
+ for color in style['color'], style['bgcolor'], style['border']:
231
+ if color and color not in color_mapping:
232
+ color_mapping[color] = offset
233
+ offset += 1
234
+
235
+ return color_mapping
236
+
237
+ @property
238
+ def _lineno_template(self):
239
+ if self.lineno_fontsize != self.fontsize:
240
+ return '{{\\fs{} \\cf{} %s{}}}'.format(self.lineno_fontsize,
241
+ self.color_mapping[self.lineno_color],
242
+ " " * self.lineno_padding)
243
+
244
+ return '{{\\cf{} %s{}}}'.format(self.color_mapping[self.lineno_color],
245
+ " " * self.lineno_padding)
246
+
247
+ @property
248
+ def _hl_open_str(self):
249
+ return rf'{{\highlight{self.color_mapping[self.hl_color]} '
250
+
251
+ @property
252
+ def _rtf_header(self):
253
+ lines = []
254
+ # rtf 1.8 header
255
+ lines.append('{\\rtf1\\ansi\\uc0\\deff0'
256
+ '{\\fonttbl{\\f0\\fmodern\\fprq1\\fcharset0%s;}}'
257
+ % (self.fontface and ' '
258
+ + self._escape(self.fontface) or ''))
259
+
260
+ # color table
261
+ lines.append('{\\colortbl;')
262
+ for color, _ in self.color_mapping.items():
263
+ lines.append(self.hex_to_rtf_color(color))
264
+ lines.append('}')
265
+
266
+ # font and fontsize
267
+ lines.append('\\f0\\sa0')
268
+ if self.fontsize:
269
+ lines.append('\\fs%d' % self.fontsize)
270
+
271
+ # ensure Libre Office Writer imports and renders consecutive
272
+ # space characters the same width, needed for line numbering.
273
+ # https://bugs.documentfoundation.org/show_bug.cgi?id=144050
274
+ lines.append('\\dntblnsbdb')
275
+
276
+ return lines
277
+
278
+ def format_unencoded(self, tokensource, outfile):
279
+ for line in self._rtf_header:
280
+ outfile.write(line + "\n")
281
+
282
+ tokensource = self._split_tokens_on_newlines(tokensource)
283
+
284
+ # first pass of tokens to count lines, needed for line numbering
285
+ if self.linenos:
286
+ line_count = 0
287
+ tokens = [] # for copying the token source generator
288
+ for ttype, value in tokensource:
289
+ tokens.append((ttype, value))
290
+ if value.endswith("\n"):
291
+ line_count += 1
292
+
293
+ # width of line number strings (for padding with spaces)
294
+ linenos_width = len(str(line_count+self.linenostart-1))
295
+
296
+ tokensource = tokens
297
+
298
+ # highlight stream
299
+ lineno = 1
300
+ start_new_line = True
301
+ for ttype, value in tokensource:
302
+ if start_new_line and lineno in self.hl_lines:
303
+ outfile.write(self._hl_open_str)
304
+
305
+ if start_new_line and self.linenos:
306
+ if (lineno-self.linenostart+1)%self.linenostep == 0:
307
+ current_lineno = lineno + self.linenostart - 1
308
+ lineno_str = str(current_lineno).rjust(linenos_width)
309
+ else:
310
+ lineno_str = "".rjust(linenos_width)
311
+ outfile.write(self._lineno_template % lineno_str)
312
+
313
+ while not self.style.styles_token(ttype) and ttype.parent:
314
+ ttype = ttype.parent
315
+ style = self.style.style_for_token(ttype)
316
+ buf = []
317
+ if style['bgcolor']:
318
+ buf.append('\\cb%d' % self.color_mapping[style['bgcolor']])
319
+ if style['color']:
320
+ buf.append('\\cf%d' % self.color_mapping[style['color']])
321
+ if style['bold']:
322
+ buf.append('\\b')
323
+ if style['italic']:
324
+ buf.append('\\i')
325
+ if style['underline']:
326
+ buf.append('\\ul')
327
+ if style['border']:
328
+ buf.append('\\chbrdr\\chcfpat%d' %
329
+ self.color_mapping[style['border']])
330
+ start = ''.join(buf)
331
+ if start:
332
+ outfile.write(f'{{{start} ')
333
+ outfile.write(self._escape_text(value))
334
+ if start:
335
+ outfile.write('}')
336
+ start_new_line = False
337
+
338
+ # complete line of input
339
+ if value.endswith("\n"):
340
+ # close line highlighting
341
+ if lineno in self.hl_lines:
342
+ outfile.write('}')
343
+ # newline in RTF file after closing }
344
+ outfile.write("\n")
345
+
346
+ start_new_line = True
347
+ lineno += 1
348
+
349
+ outfile.write('}\n')
lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pygments.formatters.svg
3
+ ~~~~~~~~~~~~~~~~~~~~~~~
4
+
5
+ Formatter for SVG output.
6
+
7
+ :copyright: Copyright 2006-2024 by the Pygments team, see AUTHORS.
8
+ :license: BSD, see LICENSE for details.
9
+ """
10
+
11
+ from pip._vendor.pygments.formatter import Formatter
12
+ from pip._vendor.pygments.token import Comment
13
+ from pip._vendor.pygments.util import get_bool_opt, get_int_opt
14
+
15
+ __all__ = ['SvgFormatter']
16
+
17
+
18
+ def escape_html(text):
19
+ """Escape &, <, > as well as single and double quotes for HTML."""
20
+ return text.replace('&', '&amp;'). \
21
+ replace('<', '&lt;'). \
22
+ replace('>', '&gt;'). \
23
+ replace('"', '&quot;'). \
24
+ replace("'", '&#39;')
25
+
26
+
27
+ class2style = {}
28
+
29
+ class SvgFormatter(Formatter):
30
+ """
31
+ Format tokens as an SVG graphics file. This formatter is still experimental.
32
+ Each line of code is a ``<text>`` element with explicit ``x`` and ``y``
33
+ coordinates containing ``<tspan>`` elements with the individual token styles.
34
+
35
+ By default, this formatter outputs a full SVG document including doctype
36
+ declaration and the ``<svg>`` root element.
37
+
38
+ .. versionadded:: 0.9
39
+
40
+ Additional options accepted:
41
+
42
+ `nowrap`
43
+ Don't wrap the SVG ``<text>`` elements in ``<svg><g>`` elements and
44
+ don't add a XML declaration and a doctype. If true, the `fontfamily`
45
+ and `fontsize` options are ignored. Defaults to ``False``.
46
+
47
+ `fontfamily`
48
+ The value to give the wrapping ``<g>`` element's ``font-family``
49
+ attribute, defaults to ``"monospace"``.
50
+
51
+ `fontsize`
52
+ The value to give the wrapping ``<g>`` element's ``font-size``
53
+ attribute, defaults to ``"14px"``.
54
+
55
+ `linenos`
56
+ If ``True``, add line numbers (default: ``False``).
57
+
58
+ `linenostart`
59
+ The line number for the first line (default: ``1``).
60
+
61
+ `linenostep`
62
+ If set to a number n > 1, only every nth line number is printed.
63
+
64
+ `linenowidth`
65
+ Maximum width devoted to line numbers (default: ``3*ystep``, sufficient
66
+ for up to 4-digit line numbers. Increase width for longer code blocks).
67
+
68
+ `xoffset`
69
+ Starting offset in X direction, defaults to ``0``.
70
+
71
+ `yoffset`
72
+ Starting offset in Y direction, defaults to the font size if it is given
73
+ in pixels, or ``20`` else. (This is necessary since text coordinates
74
+ refer to the text baseline, not the top edge.)
75
+
76
+ `ystep`
77
+ Offset to add to the Y coordinate for each subsequent line. This should
78
+ roughly be the text size plus 5. It defaults to that value if the text
79
+ size is given in pixels, or ``25`` else.
80
+
81
+ `spacehack`
82
+ Convert spaces in the source to ``&#160;``, which are non-breaking
83
+ spaces. SVG provides the ``xml:space`` attribute to control how
84
+ whitespace inside tags is handled, in theory, the ``preserve`` value
85
+ could be used to keep all whitespace as-is. However, many current SVG
86
+ viewers don't obey that rule, so this option is provided as a workaround
87
+ and defaults to ``True``.
88
+ """
89
+ name = 'SVG'
90
+ aliases = ['svg']
91
+ filenames = ['*.svg']
92
+
93
+ def __init__(self, **options):
94
+ Formatter.__init__(self, **options)
95
+ self.nowrap = get_bool_opt(options, 'nowrap', False)
96
+ self.fontfamily = options.get('fontfamily', 'monospace')
97
+ self.fontsize = options.get('fontsize', '14px')
98
+ self.xoffset = get_int_opt(options, 'xoffset', 0)
99
+ fs = self.fontsize.strip()
100
+ if fs.endswith('px'):
101
+ fs = fs[:-2].strip()
102
+ try:
103
+ int_fs = int(fs)
104
+ except ValueError:
105
+ int_fs = 20
106
+ self.yoffset = get_int_opt(options, 'yoffset', int_fs)
107
+ self.ystep = get_int_opt(options, 'ystep', int_fs + 5)
108
+ self.spacehack = get_bool_opt(options, 'spacehack', True)
109
+ self.linenos = get_bool_opt(options,'linenos',False)
110
+ self.linenostart = get_int_opt(options,'linenostart',1)
111
+ self.linenostep = get_int_opt(options,'linenostep',1)
112
+ self.linenowidth = get_int_opt(options,'linenowidth', 3*self.ystep)
113
+ self._stylecache = {}
114
+
115
+ def format_unencoded(self, tokensource, outfile):
116
+ """
117
+ Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
118
+ tuples and write it into ``outfile``.
119
+
120
+ For our implementation we put all lines in their own 'line group'.
121
+ """
122
+ x = self.xoffset
123
+ y = self.yoffset
124
+ if not self.nowrap:
125
+ if self.encoding:
126
+ outfile.write(f'<?xml version="1.0" encoding="{self.encoding}"?>\n')
127
+ else:
128
+ outfile.write('<?xml version="1.0"?>\n')
129
+ outfile.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" '
130
+ '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/'
131
+ 'svg10.dtd">\n')
132
+ outfile.write('<svg xmlns="http://www.w3.org/2000/svg">\n')
133
+ outfile.write(f'<g font-family="{self.fontfamily}" font-size="{self.fontsize}">\n')
134
+
135
+ counter = self.linenostart
136
+ counter_step = self.linenostep
137
+ counter_style = self._get_style(Comment)
138
+ line_x = x
139
+
140
+ if self.linenos:
141
+ if counter % counter_step == 0:
142
+ outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" {counter_style} text-anchor="end">{counter}</text>')
143
+ line_x += self.linenowidth + self.ystep
144
+ counter += 1
145
+
146
+ outfile.write(f'<text x="{line_x}" y="{y}" xml:space="preserve">')
147
+ for ttype, value in tokensource:
148
+ style = self._get_style(ttype)
149
+ tspan = style and '<tspan' + style + '>' or ''
150
+ tspanend = tspan and '</tspan>' or ''
151
+ value = escape_html(value)
152
+ if self.spacehack:
153
+ value = value.expandtabs().replace(' ', '&#160;')
154
+ parts = value.split('\n')
155
+ for part in parts[:-1]:
156
+ outfile.write(tspan + part + tspanend)
157
+ y += self.ystep
158
+ outfile.write('</text>\n')
159
+ if self.linenos and counter % counter_step == 0:
160
+ outfile.write(f'<text x="{x+self.linenowidth}" y="{y}" text-anchor="end" {counter_style}>{counter}</text>')
161
+
162
+ counter += 1
163
+ outfile.write(f'<text x="{line_x}" y="{y}" ' 'xml:space="preserve">')
164
+ outfile.write(tspan + parts[-1] + tspanend)
165
+ outfile.write('</text>')
166
+
167
+ if not self.nowrap:
168
+ outfile.write('</g></svg>\n')
169
+
170
+ def _get_style(self, tokentype):
171
+ if tokentype in self._stylecache:
172
+ return self._stylecache[tokentype]
173
+ otokentype = tokentype
174
+ while not self.style.styles_token(tokentype):
175
+ tokentype = tokentype.parent
176
+ value = self.style.style_for_token(tokentype)
177
+ result = ''
178
+ if value['color']:
179
+ result = ' fill="#' + value['color'] + '"'
180
+ if value['bold']:
181
+ result += ' font-weight="bold"'
182
+ if value['italic']:
183
+ result += ' font-style="italic"'
184
+ self._stylecache[otokentype] = result
185
+ return result